类别
1.无序数组
概念:未经过排序的数组
优点:插入快
缺点:查找慢,删除慢,大小固定
2.有序数组
概念:数组中的元素是按照一定规则排列的。
优点:查找效率高。根据元素值查找时可以使用二分查找,效率比无序数组高很多,在数据量大的时候尤其明显。对于leetcode中很多查找元素类的题目,如果没有事先说明是有序数组,可以事先对数组进行排序,再进行查找,二分法或其他方法都可以。
缺点:插入和删除较慢。插入元素时,首先判断该元素的小标,然后对该小标之后的所有元素后移以为才能进行插入,所以有序数组比较适合查找频繁,而插入删除操作较少的情况。
1.二维数组中的查找
题目描述:
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解题思路:
由于该二维数组上到下递增,左到右递增的特殊性,遍历整个矩阵进行查找不是该题目的意图所在。总结规律我们可以发现:应该从矩阵的右上角或者左下角开始查找。
以右上角为例,首先选取右上角的数字,如果该数字等于要查找的数字,则查找过程结束;如果该数字大于要查找的数字,则说明该列其他元素都大于要查找的数字,便可以删掉该列;如果该数字小于要查找的数字,则说明该行其他元素也都小于要查找的数字,便可以删掉该行。
这样,每一次比较都可以剔除一行或者一列,进而缩小查找范围,时间复杂度为O(n)。
java代码:
//从左下角开始查找
class Solution{
public boolean find(int [][]arrays,int target)
{
if(arrays==null)
return false;
int row=arrays.length;//行数
int col=arrays[0].length;//列数
for(int i=row-1,j=0;i>=0 && j<col;)
{
if(arrays[i][j]==target)
return true;
else if(arrays[i][j]>target) //不可能在该行
i--;
else //不可能在该列
j++;
}
return false;
}
}
2.旋转数组的最小数字
题目描述:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 给出的所有元素都大于0,若数组大小为0,请返回0。
解题思路:
如果整个数组是有序的,那我们一定会想到用折半查找来实现。对于旋转数组,我们发现,它实际上可以划分为两个排序的子数组,而且前面数组的元素都不小于后面数组的元素,并且最小值正好就是这两个数组的分界线,由此,我们可以得出以下解决方法。
首先用两个指针low和high分别指向数组的第一个元素和最后一个元素,然后可以找到中间元素mid。对于这个中间元素,有以下两种情况:(1)该元素大于等于low指向的元素,此时最小的元素说明在mid的后面,可以把low=mid;(2)中间元素小于等于high指向的元素,那么最小元素在mid之前,可以high=mid。特别注意:这里不要+1或者-1,因为只有这样才能保证low始终在第一个数组,high始终在第二个数组。依次循环,当最后low和high相差1时,low指向第一个数组的最后一个,high指向第二个数组的第一个(即为我们要找的最小值)。
很明显,以上查找的时间复杂度为O(logN)。
java代码:
public int minNumberInRotateArray(int [] array) {
/*
三种情况:
(1)把前面0个元素搬到末尾,也就是排序数组本身,第一个就是最小值
(2)一般情况二分查找,当high-low=1时,high就是最小值
(3)如果首尾元素和中间元素都相等时,只能顺序查找
*/
int len=array.length;
if(len==0)
return 0;
int low=0,high=len-1;
if(array[low]<array[high]) //排序数组本身
return array[low];
while(low<high){
int mid=low+(high-low)/2;
if(array[low]==array[mid] && array[high]==array[mid])
return minInOrder(array);//这种情况只能顺序查找
if(array[mid]>=array[low])
low=mid;
else if(array[mid]<=array[high])
high=mid;
if(high-low==1)
return array[high];
}
return -1;
}
public int minInOrder(int [] array) { //顺序查找
int min=array[0];
for(int num:array){
if(num<min)
min=num;
}
return min;
}
3.K-Sum
这类题目通常会给定一个数组和一个值,让求出这个数组中两个/三个/K个值的和等于这个给定的值target。
例题:TWO SUM(在一个数组中找到两个数字的和为目标值的数)
例如:target=5 arrays[5]={12,3,4,5,1} 数组中1+4等于目标值5,则返回它们对应的下标,不存在则返回0
解题思路:
1.暴力解法:最常见,但耗时较长,只能作为备选,
2.hash-map:建立一个hash-map循环遍历一次即可
3.two-pointers:定位两个指针根绝和的大小来移动另外一个。这里设定的指针个数根据题目中K的个数来定。3Sum中可以设定3个指针,固定两个,移动另一个。
//暴力法
class Solution{
public int Two_Sum(int arrays[],int target)
{
for(int i=0;i<arrays.length;i++)
{
for(int j=1;j<arrays.length;j++)
{
if(arrays[i]+arrays[j]==target)
{
return i,j;
}
}
}
}
}
c++代码:
bool hash_twosum(int *array,int len,int target,int *i,int *j)//i,j表示结果的下标,开始为0
{
map<int,int>index; //索引
map<int,int>::iterator it; //值迭代器
for(int x=0;x<len;x++)
{
index[array[x]]=x;//数组元素为 11,12,13,14,15 则索引index[11]=0 index[12]=1 index[13]=2 index[14]=3 index[15]=4
}
for(x=0;x<len;x++)
{
int other=target-array[x]; //剩下的数是否在数组中
it=index.find(other); //若存在这个索引
if(it!=index.end()&& it!=index.find(array[x]))//若找到或没到末尾
{
*i=x;
*j=it->second;
return true;
}
}
return false;
}
//双指针法:
class Solution{
public int Two_Sum(int arrays[],int target)
{
int left=0;
int right=arrays.length-1;
while(left<right)
{
if(arrays[left]+arrays[right]==target)
return left,right;
else if(arrays[left]+arrays[right]<target)
left++;
else if(arrays[left]+arrays[right]>target)
right--;
}
}
}
拓展:在二叉树中查找two sum
解题思路:
中序遍历二叉树,将结果放入一个数组中,再用双指针来查找
//关键代码
void inoder(BTREE T,vector<int> &a)
{
if(T==NULL)
return ;
inoder(T->lchild,a);
a.push_back(T->data);
inoder(T->rchild,a);
}
bool findtarget(BTREE T,int target)
{
vector<int> a;
inoder<T,a>; //a存放节点值
int left=0,right=n-1;
while(left<right)
{
if(a[left]+a[right] ==target)
return left,right;
else if(a[left]+a[right] <target)
left++;
else if(a[left]+a[right] >target)
right--;
else
return true;
}
return false;
}
4.判断数组中是否有重复数字
解题思路:用hash表直接一遍搞定,若有重复数字,则计数器加1
bool isrepeat(vector<int> &nums)
{
map<int,int> dict;//用hash表,初始值全部为0
for(num:nums)
{
dict[num]++;//数字相同的对应的下标的值加1
if(dict[num]>1)
return true;
}
return false;
}
5.区间问题
这类题目通常会给一个包含多个子数组的数组,然后针对区间是否重合来判断true or false。
解题技巧:
1.按start排序
2.在前一个区间的end和后一个区间的start找交集
例题:252 meeting room[easy](https://leetcode.com/problems/meeting-rooms/)
题目理解:给定一个数组,包含每个会议的开始时间和结束时间[[s1,e1],[s2,e2],…],判断一个人能否参加所有的会议。
test case:
Example1:
Input: [[0,30],[5,10],[15,20]]Output: falseExample2:Input: [[7,10],[2,4]]Output: true
解题思路:在这个题目里,如果一个人要参加所有会议,那么所有会议的时间应该不能重合,所以只需要判断后一个会议开始的start > 前一个会议结束的end就就可以,如果end>start,就返回false。首先得先对所有子数组的start进行排序,然后再进行判断start和end
class Solution{
public boolean canAttendMeetings(Interval[] intervals){
Arrays.sort(intervals,(x,y)->(x.start-y.start));
//i从1开始,因为涉及到判断前一个数组的end
for (int i =1;i <intervals.length;i++){
if (intervals[i-1].end > intervals[i].start){
return false;
}
}
}
6.子数组类题目
这类题目通常会在一个包含多个子数组的数组中,求和/积,最大最小等。
解题技巧:
滑动窗口(sliding window)
例题:209 Minimum Size Subarray Sum[Medium]
题目理解:给定我们一个数字,让我们求子数组之和大于等于给定值的最小长度
test case:
Input: s = 7, nums = [2,3,1,2,4,3]
Output: 2
解释:满足子数组和=7的最小长度数组是[4,3],所以output=2
解题思路:求的数字要大于等于这个数字target,譬如这个testcase中,[2,3,1,2,4,3],从前往后相加,前四项相加的和为8.已经大于7了,但是我们的target是7,后面的几项也都是正整数,继续往后走,肯定也会大于target7,所以这个时候我们把left指针往右移动一位,那么就是相当于成为了一个滑动窗口(sliding window)。
7.数组输出排列最小的数
例如: [2,23,234] 最小结果为223234
解题思路:因为涉及到数字可能会溢出,所以用字符串较合适,转换为字符串–> a+b b+a 比较 2 23–>23 2,将较小的数连在一起
#include
#include
#include
using namespace std;
static bool cmp(int a,int b)
{
std::string A=std::to_string(a)+std::to_string(b);
std::string B=std::to_string(b)+std::to_string(a);
return A<B;
}
string print(vector<int> numbers)
{
sort(numbers.begin(),numbers.end(),cmp);
string s;
for(int ai:numbers)
{
s+=to_string(ai);
}
return s;
}
int main()
{
vector<int> numbers;
print(numbers);
return 0;
}
8.去掉有序数组中重复的数字,返回无重复数字的长度
解题思路:双指针法(覆盖法)
//i代表旧数组的索引,index代表新数组的索引,将无重复数依次移动到前面
int remove(vector<int> &a)
{
int i,index;
index=1;//index为第二个数的索引
if(a.empty())
return 0;
for(i=1;i<a.size();i++)//i和index均为第二个数的索引
{
if(a[i]!=a[index-1])
{
a[index]=a[i];
index++;//记录不重复数的个数
}
}
return index;
}
9、调整数组顺序使奇数位于偶数前
解题思路:
1.用新的数组存储新的顺序,先扫描一遍数组,将奇数存入新的数组中,再扫描一遍数组将偶数存入数组中。比较耗费空间,不建议。
2.用双指针,若查找到一个奇数和一个偶数,则将它们互换位置。
//法一
#include
#include
using namespace std;
void ReOrderArray(vector<int> &a)
{
if(a.empty())
return ;
vector<int> result;
for(int i=0;i<a.size();i++)
{
if(a[i]<<2==1)
result.push_back(a[i]);
}
for(i=0;i<a.size();i++)
{
if(a[i]<<2==0)
result.push_back(a[i]);
}
a=result;
}
int main()
{
vector<int> a;
int b[5]={1,2,3,4,5};
for(int i=0;i<5;i++)
{
a.push_back(b[i]);
}
ReOrderArray(a);
for(i=0;i<a.size();i++)
{
cout<<a[i]<<" ";
}
return 0;
}
题目描述:例如数组[1,2,3].其排列组合结果为[1,3,2],[2,1,3],[2,3,1],[3,2,1],[3,1,2],[1,2,3]
思路:把数组看成两部分,一部分是第一个数,另一部分是第一个数后面的所有数,拿第一个数与后面的所有数依次交换,很典型的递归思路;
void Fun()
{
int a[5]={1,2,3,4,5];
permutations(a,0,4);
}
void permutations(int a[],int m,int n)
{
if(m==n)
{
for(int i=0;i<=n;i++)
{
cout<<a[i];
}
cout<<endl;
}
else
{
for(int j=m;j<=n;j++)
{
int temp=a[m];
a[m]=a[i];
a[i]=temp;
permutations(a,m+1,n);
temp=a[m];
a[m]=a[i];
a[i]=temp;
}
}
}
拓展:如果是求字符串的排列组合呢?
如果是求字符串的所有组合呢:例如有三个字符a,b,c,则它们的组合为a,b,c,ab,ac,bc,abc.