数组数据结构的基本概念
1.数组的空间效率不是很高,空闲区域没有充分利用。创建数组必须根据大小分配内存,即使数组中只存储一个元素,也要为所有的数据预先分配内存。
2.数组的时间效率非常高。可以在O(1)读取任何元素。利用数组的下标与数组value构成一个键值的哈希表。实现快速查找
目录
- [1.数组和指针问题##sizeof](#array and pointer sizeof)
- [2.数组字符串##1-bit or 2-bit](#717.1-bit and 2-bit Characters )
- [3.数组分割##Array partition](#Array Partition I )
- [4.数组三元素之和##3Sum](#15. 3Sum)
- [5.数值三元素之和变异版 ##16.3Sum Closest](#16.3Sum Closest)
- [6.数组网##989.Array Nesting](#989.Array Nesting)
- 7.完美的数组排列##667.beautiful-arrangement-ii)
- [8.实现水容器最大化##11.Container With Most Water](#11.Container With Most Water)
- [9.旋转数组中查找##33Search in Rotated Sorted Array](#33.Search in Rotated Sorted Array)
- [10.返回数组中目标值的index范围##34Find First and Last Position of Element in Sorted Array](#34.Find First and Last Position of Element in Sorted Array)
- [11.数组元素的结合结果-回溯算法## combination sum](#39.Combination Sum)
- [12.找到连续数字数组中的重复数字## find the duplicate number](#287.Find the Duplicate Number)
- [13.找到数组中所有的重复数字## find all duplicates in array](#442.Find All Duplicates in an Array)
- [14.二维数组的查找## search in 2D matrix](#74.Search a 2D matrix)
- [15.二维数组查找的进阶## 剑指offer](#4.Search in 2D array)
阶段总结-2018.10.25
数组的数据结构
1.欠缺的知识点,数组和哈希表结合解决数组中重复元素的问题
2.数组不是引用,直接使用会退化为指针
3.一般用到二分查找的时候。middle=(start+end-1)>>1 ;middle=start+((end-start)>>1) 注意使用。
赋值时 midddle=end;start=midddle+1; 这样的最后的结果是start==end。
4.回溯算法还没有掌握
数组和指针的问题
#include<stdio.h>
#include<stdlib.h>
int GetSize(int *data){
return sizeof(data); // sizeof 是返回数组整个的字节数
}
int main(){
int data1[]={1,2,3,4,5};
int size1=sizeof(data1);
int *data2=data1; // 任意指针的sizeof 32位系统返回 4 ,64位系统返回 8
int size2=sizeof(data2);
int size3=GetSize(data1); // 数组作为函数的参数进行传递时,数组自动退化为同类型的指针
printf("%d,%d,%d
",size1,size2,size3);
}
// 输出结果是 20,8,8
717.1-bit and 2-bit Characters
我自己的想法:数组最后一个一定是个0,考虑数组倒数第二个元素。如果是为0,则返回true;如果是1,计算除最后一个元素的数组长度的奇偶,如果是奇数则返回true.这个想法漏洞百出
参考别人的解题思路:
1. We don't need to traverse the whole array, just check the last part of it.
if there is only one symbol in array the answer is always true (as last element is 0)
3.if there are two 0s at the end again the answer is true no matter what the rest symbols are( ...1100, ...1000,)
4if there is 1 right before the last element(...10), the outcome depends on the count of sequential 1, i.e.
a) if there is odd amount of 1(10, ...01110, etc) the answer is false as there is a single 1 without pair
b) if it's even (110, ...011110, etc) the answer is true, as 0 at the end doesn't have anything to pair with
class Solution {
public:
bool isOneBitCharacter(vector<int>& bits) {
int len=bits.size();
if(len==1)
return true;
int ones=0;
for(int i=len-2;i>=0&&bits[i]!=0;i--){
ones++;
}
if(ones%2>0) return false;
return true;
}
};
Array Partition I
思路:
1.直接将数组进行排序
2.将排好序数组的偶数位进行累加
# python比较容易实现
class Solution:
def arrayPairSum(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
num_sorted=sorted(nums)
sum=0
for i in range(0,len(num_sorted),2):
sum+=num_sorted[i]
return sum
15. 3Sum
# use python
# 注意!!!!!这是个没有AC的代码
class Solution:
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
sloution=[]
if(len(nums)<3):
return []
for i in range(len(nums)-1):
for j in range(i+1,len(nums)-1):
if -(nums[i]+nums[j]) in nums[j+1:]:
sloution.append([nums[i],nums[j],-(nums[i]+nums[j])])
# 这个还是不对
if(len(sloution)<=1):
return sloution
for i in range(len(sloution)-1):
for j in range(i+1,len(sloution)-1):
if set(sloution[i])==set(sloution[j]):
sloution.remove(sloution[j])
return sloution
# 将结果进行 元组化 去除重复元素
"""
for i in sloution:
for j in sloution[i+1:]: # 没办法进行切片。i不是数字
if set(i)==set(j):
sloution.remove(j)
return sloution
"""
if __name__ == '__main__':
nums=[-1,0,1,2,-1,-4]
slou=Solution()
result=slou.threeSum(nums)
print(result)
CPP版本
1.先将数组进行排序,有利于后序的查找工作 ,可以使用双指针的(二分查找)来快速查找需要的元素
2.利用循环,来排除掉相同的元素。
// date:2018.10.15
// @author:dengshuo
// first introduce a naive solution for the duplicates will be
// using the stl methods like below:
std::sort(res.begin(),res.end());
res.earse(unique(res.begin(),res.end()),res.end());
vector<vector<int>> three_sum(vector<int>&num){
vector<vector<int>> result;
std::sort(num.begin(),num.end()); // 进行排序
for(int i=0;i<num.size();i++){
int target=-num[i];
int front=i+1;
int back=num.size()-1;
while(front<back){
int sum=num[front]+num[back];
if(sum<target)
front++;
else if(sum>target){
back--;
}
else{
vector<int> triplet(3,0);
triplet[0]=num[i];
triplet[1]=num[front];
triplet[2]=num[back];
result.push_back(triplet);
// 处理第二个元素的重复项元素
while(front<back && num[front]==triplet[1]) front++;
// 处理第三个元素的重复元素,进行跳过
while(front<back && num[back]==triplet[2]) back--;
}
}
// 处理第一项的重复元素
while(i+1<num.size()&& num[i+1]==num[i])
i++;
}
return result;
}
16.3Sum Closest
// 整体来说这个实现还简单一点,只返回与target 差值最小的和值。
// 无需新建数组来存储元素
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
std::sort(nums.begin(),nums.end());
int sum=nums[0]+nums[1]+nums[2];
for(int i=0;i<nums.size();i++){
int front=i+1;
int back=nums.size()-1;
while(front<back){
int result=nums[i]+nums[front]+nums[back];
if(abs(sum-target)>abs(result-target)){
sum=result;
}
if(result<target){
front++;
}
else{
back--;
}
}
}
return sum;
}
};
989.Array Nesting
# python version 暴力解法,循环硬刚
# 结果会出现time limit excessed 运行时间问题,没有AC
class Solution:
def arrayNesting(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if(len(nums)==0):
return 1
list_array=[]
for i in range(len(nums)-1):
longest_k=[]
temp=nums[i]
while(temp not in longest_k):
longest_k.append(temp)
temp=nums[temp]
list_array.append(longest_k)
max_l=0
for i in list_array:
if(len(i)>max_l):
max_l=len(i)
return max_l;
if __name__ == '__main__':
sol=Solution()
nums=[5,4,0,3,1,6,2]
result=sol.arrayNesting(nums)
print(result)
# 遇到太长的数组,就会time limit exceeded
class Solution:
def arrayNesting(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if(len(nums)==0):
return 1
list_array=[]
for i in range(len(nums)-1):
longest_k=[]
temp=nums[i]
while(temp):
longest_k.append(temp)
while(i!=temp && nums[temp]!=i):
temp=nums[temp]
max_l=0;
c++问题就是,循环链表进行标记
The idea is to start from every number, find circles in those index-pointer-chains, every time you find a set (a circle) mark every number as visited (-1) so that next time you won't step on it again.
class Solution {
public:
int arrayNesting(vector<int>& nums) {
int max_size=0;
for(int i=0;i<nums.size();i++){
int temp_size=0;
for(int j=i;nums[j]>=0;temp_size++){
int temp=nums[j];
// 当数组出现后,将数组标记为-1,也就是在这个集合中出现过。
nums[j]=-1;
j=temp;
}
// 循环一次就要与最大值进行比对,找到最大值
max_size=max(max_size,temp_size);
}
return max_size;
}
};
667.beautiful-arrangement-ii
还有一个未解决的问题,就是判断条件问题。特别是k=8,和k=5这两种情况的第一个输入问题。
class Solution {
public:
vector<int> constructArray(int n, int k) {
vector<int> result;
for(int i=1,j=n;i<=j;){
if(k>1){
result.push_back(k--%2? i++:j--);
}
else
result.push_back(i++);
}
return result;
/*
// 边界条件判断
if(n<=1 ||n<=k ||k<1){
return {};
}
vector<int> sort_array;
for(int i=1;i<=n;i++)
sort_array.push_back(i);
if(k==1){
return sort_array;
}
for(int j=0;j<k-1;j++){
int temp=sort_array[j];
sort_array[j]=sort_array[j+1];
sort_array[j+1]=temp;
}
return sort_array;
*/
}
};
// start from i = 1, j = n;
// i++, j--, i++, j--, i++, j--
// k=8
i: 1 2 3 4 5
j: 9 8 7 6
out: 1 9 2 8 3 7 4 6 5
dif: 8 7 6 5 4 3 2 1
// k=5
i++ j-- i++ j-- i++ i++ i++ ...
out: 1 9 2 8 3 4 5 6 7
dif: 8 7 6 5 1 1 1 1
11.Container With Most Water
// 时间复杂度为O(n^2)
// 实现的效率较低
class Solution {
public:
int maxArea(vector<int>& height) {
int max_area=0;
for(int i=0;i<height.size()-1;i++){
for(int j=i+1;j<height.size();j++){
int temp_area=0;
temp_area=min(height[i],height[j]) *(j-i);
if (temp_area>max_area){
max_area=temp_area;
}
}
}
return max_area;
}
};
// 采用“双指针”方法
// 时间复杂度为O(logN)??.时间复杂度要好很多
class Solution {
public:
int maxArea(vector<int>& height) {
int temp1=0;
int temp2=height.size()-1;
int temp_area=0;
int max_area=0;
while(temp1<temp2){
if(height[temp1]>height[temp2]){
temp_area=height[temp2] * (temp2-temp1);
temp2--;
}
else{
temp_area=height[temp1] * (temp2-temp1);
temp1++;
}
if(temp_area>max_area){
max_area=temp_area;
}
}
return max_area;
}
};
33.Search in Rotated Sorted Array
//未理解版本,想用双指针来解决问题
class Solution {
public:
int search(vector<int>& nums, int target) {
int front=0;
int back=nums.size()-1;
while(front<back){
if(nums[back]!=target && nums[front]!=target){
front++;
back--;
}
if(nums[front]==target){
return front;
}
if(nums[back]==target){
return back;
}
}
return -1;
}
};
// 利用二分查找
// 第一步是找出旋转列表中的最小值
// 第二步 还是二分查找 利用最小值的下标,来确定真正要查找值的index
class Solution {
public:
int search(vector<int>& nums, int target) {
int lo=0;
int hi=nums.size()-1;
// find the index of the smallest value using binary search.
// Loop will terminate since mid < hi, and lo or hi will shrink by at least 1.
// Proof by contradiction that mid < hi: if mid==hi, then lo==hi and loop would have been terminated.
while(lo<hi){
int mid=(lo+hi)/2;
if(nums[mid]>nums[hi]){
lo=mid+1;
}
else{
hi=mid;
}
}
// find the smallest value in nums
// when loop terminate ,lo==hi
int rot=lo;
lo=0;
hi=nums.size()-1;
while(lo<=hi){
int mid=(lo+hi)/2;
int real_mid=(mid+rot)%nums.size(); //找到真正中间值的下标,这是一个很重要的数学问题
if(nums[real_mid]==target) return real_mid;
if(nums[real_mid]<target){
lo=mid+1;
}
else hi=mid-1;
}
return -1;
}
};
34.Find First and Last Position of Element in Sorted Array
// 自己的想法是:二分查找
// 第一步,先利用二分查找,找到目标值
// 第二步,找到目标值后,利用下标增减来继续查找,将值放入result中
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> result;
int front=0;
int back=nums.size()-1;
while(front<back){
int mid=(front+back)/2;
if(nums[mid]<target) lo=mid+1;
else if(nums[mid]>target) hi=mid-1;
else {
result.push_back(mid);
// 第一步完成
// 继续实现第二步
}
}
result={-1,-1};
return result;
}
};
// 瞻仰一下大神的做法,根本没有看懂
The problem can be simply broken down as two binary searches for the begining and end of the range, respectively:
First let's find the left boundary of the range. We initialize the range to [i=0, j=n-1]. In each step, calculate the middle element [mid = (i+j)/2]. Now according to the relative value of A[mid] to target, there are three possibilities:
If A[mid] < target, then the range must begins on the right of mid (hence i = mid+1 for the next iteration)
If A[mid] > target, it means the range must begins on the left of mid (j = mid-1)
If A[mid] = target, then the range must begins on the left of or at mid (j= mid)
Since we would move the search range to the same side for case 2 and 3, we might as well merge them as one single case so that less code is needed:
2*. If A[mid] >= target, j = mid;
Surprisingly, 1 and 2* are the only logic you need to put in loop while (i < j). When the while loop terminates, the value of i/j is where the start of the range is. Why?
No matter what the sequence originally is, as we narrow down the search range, eventually we will be at a situation where there are only two elements in the search range. Suppose our target is 5, then we have only 7 possible cases:
case 1: [5 7] (A[i] = target < A[j])
case 2: [5 3] (A[i] = target > A[j])
case 3: [5 5] (A[i] = target = A[j])
case 4: [3 5] (A[j] = target > A[i])
case 5: [3 7] (A[i] < target < A[j])
case 6: [3 4] (A[i] < A[j] < target)
case 7: [6 7] (target < A[i] < A[j])
For case 1, 2 and 3, if we follow the above rule, since mid = i => A[mid] = target in these cases, then we would set j = mid. Now the loop terminates and i and j both point to the first 5.
For case 4, since A[mid] < target, then set i = mid+1. The loop terminates and both i and j point to 5.
For all other cases, by the time the loop terminates, A[i] is not equal to 5. So we can easily know 5 is not in the sequence if the comparison fails.
In conclusion, when the loop terminates, if A[i]==target, then i is the left boundary of the range; otherwise, just return -1;
For the right of the range, we can use a similar idea. Again we can come up with several rules:
If A[mid] > target, then the range must begins on the left of mid (j = mid-1)
If A[mid] < target, then the range must begins on the right of mid (hence i = mid+1 for the next iteration)
If A[mid] = target, then the range must begins on the right of or at mid (i= mid)
Again, we can merge condition 2 and 3 into:
2* If A[mid] <= target, then i = mid;
However, the terminate condition on longer works this time. Consider the following case:
[5 7], target = 5
Now A[mid] = 5, then according to rule 2, we set i = mid. This practically does nothing because i is already equal to mid. As a result, the search range is not moved at all!
The solution is by using a small trick: instead of calculating mid as mid = (i+j)/2, we now do:
mid = (i+j)/2+1
Why does this trick work? When we use mid = (i+j)/2, the mid is rounded to the lowest integer. In other words, mid is always biased towards the left. This means we could have i == mid when j - i == mid, but we NEVER have j == mid. So in order to keep the search range moving, you must make sure the new i is set to something different than mid, otherwise we are at the risk that i gets stuck. But for the new j, it is okay if we set it to mid, since it was not equal to mid anyways. Our two rules in search of the left boundary happen to satisfy these requirements, so it works perfectly in that situation. Similarly, when we search for the right boundary, we must make sure i won't get stuck when we set the new i to i = mid. The easiest way to achieve this is by making mid biased to the right, i.e. mid = (i+j)/2+1.
All this reasoning boils down to the following simple code:
vector<int> searchRange(int A[], int n, int target) {
int i = 0, j = n - 1;
vector<int> ret(2, -1);
// Search for the left one
while (i < j)
{
int mid = (i + j) /2;
if (A[mid] < target) i = mid + 1;
else j = mid;
}
if (A[i]!=target) return ret;
else ret[0] = i;
// Search for the right one
j = n-1; // We don't have to set i to 0 the second time.
while (i < j)
{
int mid = (i + j) /2 + 1; // Make mid biased to the right
if (A[mid] > target) j = mid - 1;
else i = mid; // So that this won't make the search range stuck.
}
ret[1] = j;
return ret;
}
39.Combination Sum
回溯算法解决一些问题
// backtarcking
// 回溯算法,是一种优化方法
// 很像深度优先遍历 dfs 采用递归的方法解决
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
std::sort(candidates.begin(),candidates.end()); // sort the input array
vector<vector<int>> res;
vector<int> combination;
combinationSum_helper(candidates,target,res,combination,0);
return res;
}
private:
void combinationSum_helper(vector<int> &candidates,int target,vector<vector<int>>&res,vector<int>&combination,int begin){
if(!target){
res.push_back(combination);
return; //return a space
}
for(int i=begin;i!=candidates.size() && candidates[i]<=target;i++){
combination.push_back(candidates[i]);
combinationSum_helper(candidates,target-candidates[i],res,combination,i);
combination.pop_back(); // remove the last element
}
}
};
287.Find the Duplicate Number
// 暴力的简单解法
// O(n^2)的时间效率,效率较差
class Solution {
public:
int findDuplicate(vector<int>& nums) {
for(int i=0;i<nums.size()-1;i++){
for(int j=i+1;j<nums.size();j++){
if (nums[i]==nums[j]){
return nums[i];
}
}
}
}
};
// 时间复杂度为O(nlogn)
// 利用含有重复数字的数组段的长度要大1的特性来解决问题
class Solution {
public:
int findDuplicate(vector<int>& nums) {
if(nums.size()==0)
return -1;
int start=1;
int end=nums.size()-1;
while(start<=end){ //时间复杂度为O(logn)
int middle=start+(end-start)/2; // also can use :start+((end-start)>>1)
int count=count_range(nums,start,middle);
if(end==start){
if(count>1)
return start;
else
break;
}
if(count>(middle-start+1))
end=middle;
else
start=middle+1;
}
return -1;
}
private:
// 这个私有函数的被调用的时间复杂度为O(n)
int count_range(vector<int>& nums,int start,int end){
int count=0;
for(int i=0;i<nums.size();i++){
if(nums[i]>=start && nums[i]<=end)
++count;
}
return count;
}
};
442.Find All Duplicates in an Array
// use hashtable ?
// don't understand
class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
// use hashtable ?
vector<int> res;
for(int i = 0; i < nums.size(); i ++){
nums[abs(nums[i])-1] = -nums[abs(nums[i])-1];
if(nums[abs(nums[i])-1] > 0)
res.push_back(abs(nums [i]));
}
return res;
// this version output error: memory time limited
// just the malloc the
/*
vector<int> result;
for(int i=0;i<nums.size();i++)
{
while(nums[i]!=i+1)
{
if(nums[i]==nums[nums[i]-1])
{
result.push_back(nums[i]);
}
int temp=nums[i];
nums[i]=nums[temp-1];
nums[temp-1]=temp;
}
}
return result;
*/
}
};
74.Search a 2D matrix
// 这个版本的二分查找,最后会将最后的值确定在r值上面
// 这个二分查找要好好理解记忆
class Solution {
public:
bool searchMatrix(vector<vector<int> > &matrix, int target) {
int n = matrix.size();
if (n==0)
return false;
int m = matrix[0].size();
if(m==0)
return false;
int l = 0, r = m * n - 1;
while (l != r){ //特别是 这几个判断及赋值语句
int mid = (l + r - 1) >> 1;
if (matrix[mid / m][mid % m] < target)
l = mid + 1;
else
r = mid;
}
return matrix[r / m][r % m] == target;
}
};
// 这个版本的实现的效率太低
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m=matrix.size();
if(m==0)
return false;
int n=matrix[0].size();
if(n==0)
return false;
int start=0,end=n*m-1;
while(start+1<end){
int middle=start+(end-start)/2;
int i=middle/n;
int j=middle%n;
if(matrix[i][j]>target)
end=middle;
else
start=middle;
}
if(matrix[start/n][start%n]==target)
return true;
if(matrix[end/n][end%n]==target)
return true;
return false;
}
};
4.二维数组中的查找
// 怎么测试都不对,需要改进
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <cstdio>
using namespace std;
bool Find(vector<vector <int> >& matrix,int target){
bool found=false;
// error the matrix vector don;t have size()
int row=matrix.size();
if(row==0)
return false;
int col=matrix[0].size();
while(row>0 && col>0){
int n=0;
int m=col-1;
while(n<row && m>=0){
if (matrix[n*col+m]==target){
found=true;
break;
}
else if(matrix[n*col+m]>target)
--m;
else
++n;
}
}
return found;
}
int main(){
vector<vector <int> > matrix={{1,2,8,9},{2,4,9,12},{4,7,10,13},{6,8,11,15}};
/*
vector< int > a;
a.push_back(1);
a.push_back(2);
a.push_back(8);
a.push_back(9);
vector< int > b;
b.push_back(2);
b.push_back(4);
b.push_back(9);
b.push_back(12);
vector< int > c;
c.push_back(4);
c.push_back(7);
c.push_back(10);
c.push_back(13);
vector< int > d;
d.push_back(6);
d.push_back(8);
d.push_back(11);
d.push_back(15);
matrix.push_back(a);
matrix.push_back(b);
matrix.push_back(c);
matrix.push_back(d);
*/
int target=7;
bool result=Find(matrix,target);
cout<<result<<endl;
return 0;
}