1.寻找最小的K个数
解法一:使用堆来代替
把维护K个元素的数组用最大堆来进行替代。时间变为O(NlogK)!
解法二:快排划分元素
分割元素确定了左边和右边的元素数量,左边的个数大于k那么前k个元素就在左数组;左边的个数小于k那么只需要在右边寻找
K-m-1个元素即可;如果等于k那么就直接找到了k个元素;
2.寻找和为定值的两个数
解法一:暴力法
找出所有的两个数对,然后求值进行比较。两个for循环即可。时间O(N平方)
解法二:散列表法
把所有数构建一个散列表,然后查找每个数对应的差,看是否在散列表中。
解法三:双指针法(先说一句,如果未排序,先进行排序)
更好的方式是使用头尾双指针法,如果sum>val,说明值需要减小end--即可,sum<val,值需要增加,start++即可。
引申扩展:三数和/四数和的问题,
全都转化为两数之和的问题即可。
#数组排序,先确定一个指针,问题转化为双指针找固定数!
class Solution:
def threeSum(self, nums):
nums.sort()
n = len(nums)
res = []
for k in range(n-2):
#-4 -1 -1 0 1 2有重复的-1是可能会得到重复结果的
if k==0 or nums[k-1]<nums[k]: #判重
i = k+1
j = n-1
while i<j:
if nums[i]+nums[j]==-nums[k]:
arr = [nums[i],nums[j],nums[k]]
res.append(arr)
i+=1
j-=1
#在这里再次判断重复[-2,0,0,2,2]
while i<j and nums[i]==nums[i-1]: #判重
i+=1
while j+1<n and i<j and nums[j]==nums[j+1]: #判重
j-=1
elif nums[i]+nums[j]>-nums[k]:
j-=1
else:
i+=1
return res
3.寻找和为定值的多个数
输入两个整数n和sum,从数列1,2,3,4...n中随意取几个数使和为sum,列出所有组合。
解法一:暴力递归
对每个数字有两种选择,选与不选的问题,无重复不用判断重复的问题。时间O(2的N次方)
ps:加一些判断条件,进行递归剪支;
扩展:最典型的01背包问题,n个物品一个容量为v的背包,矩阵的值代表第i个物品放入背包耗费的容量是Ci,得到的价值是W,怎样选择让背包中的价值最大?
构建矩阵f(i,j)代表前i个物品放入容量为j的背包中最大的价值。这就引到了放与不放f(i,j)=max(f(i-1,j),f(i-1,j-ci)+wi)状态矩阵。
4.最大连续子数组和
注意子数组是连续的,数组里的值有正,有负,有零。
解法一:分治法
最大的子数组可能在中间位置(从当前元素向左和向右扩展,找到最大的即可),可能在左边可能在右边,对子问题进行递归即可。时间O(NlogN)
解法二:动态规划
设f(i)为前i个数的最大子数组的和,dp(i)以第i个元素结尾的最大子数组和;f(i)= max(f(i-1),dp(i-1)+arr(i)),dp(i)=arr(i)+dp(i-1)if dp(i-1)>0 else arr(i)
扩展:如果要求出连续子数组的最大乘积?
设f(i)为以第i个数结尾的最大子数组的乘积,f(i)等于第i个数乘f(i-1)中的最小或者最大的数,因为第i个数可能为正/负。
一维环形子数组 最大和?(数组的首尾可以跨越)
划分子问题,跨越计算一遍,不跨越计算一遍,跨越的计算一遍(需要从头和尾元素为起点向外扩);
5.跳台阶问题
解法一:递归(自顶向下)
分解子问题,f(n)=f(n-1)+f(n-2),f(1)=1,f(2)=2;时间O(2的N次方)
解法二:动态规划(自下向上)
存在大量重复的计算,把计算出来的结果都存储下来,然后就可以避免重复的计算。时间O(N)
6.奇偶数排序(奇数放在偶数的前面)
可以改变相对位置(随机交换不具有稳定性):
解法一:两个指针向后扫
p1指向奇数边界的下一个元素,p2每次向前遍历,if p2为奇数 则p1与p2交换,然后p1++。
不能改变相对位置(稳定性的排序):
解法二:不使用额外空间,使用冒泡排序,每次在尾部确定一个偶数;使用插入排序也可以。平方时间!
7.荷兰国旗
分三色的问题,红(0),白(1),黑(2)。
解法:三指针处理
p1指向0的边界的下一个元素,p2指向2边界的下一个元素,cur是遍历指针。
p1和cur起始都是指向首元素,p2起始指向尾元素。
if cur指向0,那么与p1的元素交换,然后p1++,cur++,需要特别注意cur与p1之间只会是1。
if cur指向1,那么不用交换,cur++即可。
if cur指向2,那么与p2元素交换,然后p2--,但是cur不能动,因为交换到cur的元素可能会是0/1。
def point_three(self,arr):
cur = p1 = 0
p2 = len(arr)-1
while(cur<=p2):
if(arr[cur]==0):
arr[p1],arr[cur] = arr[cur],arr[p1]
p1+=1
cur+=1 #p1和cur之间的元素只能是1
elif(arr[cur]==1):
cur+=1
else:
arr[p2],arr[cur] = arr[cur],arr[p2]
p2-=1
print(arr)
扩展一:找出只由 a,b,c 组成的字符串中包含 abc 的个数
思路:分别找出a,b,c的个数然后相乘,荷兰国旗问题,遍历一遍即可知道。
扩展二:三路快排
思路:大于分割元素,小于分割元素,等于分割元素;
8.数组中重复的数字
所有数字都在0-n-1范围内,长度为n的数组,需要找出数组中重复的一个数字。
思路一:哈希表,不改变原数组;
思路二:遍历一遍,当第i个元素等于下标就++,不等于下标就放到对应位置,如果相等那就是该值,如果不等就交换元素,改变原数组。
9.数组中数字出现的次数
找出数组中只出现一次的两个数字,其他每个数字只出现2次。
思路:先异或一边,得到两个数的异或值val,在找出val最右边的1位记为第k位,因为异或是不同会生成1,可以根据第k位的1把数组的数分开,然后在把一半的数组进行异或那么会得到两个数字的其中一个,然后再把这个数字跟val进行异或会得到最后一个。
扩展:数组中唯一出现过一次的数,其他数字出现过三次。
思路:可以把每个数的二进制位进行对应相加,然后把res结果的每一位与3进行取余,余数为1那么target对应位的数为1否则为0。(最后再转化成十进制即可)
class Solution {
public:
int singleNumber(int A[], int n) {
int bit_time[32] = {0};
int i = 0;
int j = 0;
int result = 0;
for(i = 0; i < 32; i++){
for(j = 0; j < n; j++){
bit_time[i] += (A[j] >> i) & 0x01;
}
result |= (bit_time[i] % 3) << i;
}
return result;
}
};
10.数组中0~n-1中缺失的数字
思路一:可以计算0-n-1的加和再减去数组的和得到的差。可能会溢出!
思路二:如果有序,使用二分查找,if 中间的数与下标对应那么在右半区查找,if 不对应 且上一个数也不对应则在左半区找,否则前一个数对应的下标就是target。
思路三:使用异或法,因为知道了范围中的数字而且缺了一个,可以在异或数组的基础上在异或一遍数字。
扩展:如果找0~n-1缺失两个数,
异或法,跟0~n-1进行异或的得到缺失数异或的结果,根据结果找到一个二进制为1的位置然后会把s1和s2分开,然后在异或一半数组就可以得到一个结果。
11.如何在不排序的情况下求数组的中位数(重要)
思路:利用pratition函数进行划分,将划分元素的位置与中位数的位置进行比较,要么在左半区继续找,要么在右半区继续找。
class Solution:
def find(self,arr):
#偶数找第(n-1)/2和n/2个取平均值,奇数找第n/2和(n-1)/2取平均,计算的值恰好为对应索引
n = len(arr)
left = 0
right = n-1
first = 0
while left<=right:
l = left
r = right
val = arr[l]
start = l
while l<r:
while l<r and arr[r]>val:
r = r-1
while l<r and arr[l]<=val:#左指针的区间是小于等于val的
l = l+1
arr[l],arr[r] = arr[r],arr[l]
arr[start],arr[l] = arr[l],val
#进行中位数查值
if(l==int((n-1)/2)):
first = arr[l]
break
elif(l<int((n-1)/2)):
left = l+1
else:
right = l-1
left = 0
right = n - 1
second = 0
while left <= right:
l = left
r = right
val = arr[l]
start = l
while l < r:
while l < r and arr[r] > val:
r = r - 1
while l < r and arr[l] <= val: # 左指针的区间是小于等于val的
l = l + 1
arr[l], arr[r] = arr[r], arr[l]
arr[start], arr[l] = arr[l], val
# 进行中位数查值
if (l == int((n) / 2)):
second = arr[l]
break
elif (l < int((n) / 2)):
left = l + 1
else:
right = l - 1
print(first,second)
return
12.三个有序数组中找出公共元素
思路:因为有序,直接进行3路归并进行判断即可,找出最小的/两个相等的/三个相等的。
13.如何对大量重复数字的数组进行排序
思路:(不知道区间无法进行计数排序)利用字典进行计数统计,然后再对这个字典的key遍历到数组进行快排,在利用数组和字典确定每个元素的开始位置并且用字典存起来,然后就可以进行计数排序了。
ps:利用了计数排序的思想。
14.两个数组的交集
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]
思路一:分别排序,然后双指针进行处理。
思路二:使用hash表,先把第一个数组hash一遍,在用第二个数组进行查找(注意有重复元素的情况)。
15.找出超过一半的数?
思路一:遍历一遍,如果当前元素等于计数器元素就加一,否则减一;如果计数器为0,则更新为当前元素;
思路二:排序法;
int search(int *A,int size)
{
int count=0;
int current;
for(int i=0;i<size;i++)
{
if(count==0)
{
current=A[i];
count=1;
}
else
{
if(A[i]==current)
count++;
else
count--;
}
}
return current;
}
扩展:如果找出等于一半的数?
思路:如果该数在最后,那么先进行检测一下;如果不在最后正常方法
16.求一个数组b, b[i] = a[0] * a[1] *…*a[i – 1] * a[i + 1] * …*a[n – 1],不能使用除法,不允许再开数组
思路:正向遍历初始化1,index从1~n-1乘一遍;反向遍历初始化1,index从n-2~1乘一遍(前缀和)
int main()
{
int a[]={2,3,7,23,6,5,1,23,89,23};
int *b=(int*)malloc(sizeof(a));
b[0]=1;
int len=sizeof(a)/sizeof(int);
int j,i;
for( i=1;i<len;i++)
{
b[i]=b[i-1]*a[i-1];
}
int tmp=1;
for(j=len-2;j>=1;j--)
{
tmp*=a[j+1];
b[j]*=tmp;
}
pr_arr(b,len);
return 0;
}
17.给定一个01串,求它一个最长的子串满足0和1的个数相等。
思路:把0换成-1,求和为0即可;dp(i)代表前i个元素最长的子串和为0;
18.给定n * n的01方阵,每一行都是降序的(即先连续的一段1,再连续的一段0),求1最多的那行中1的个数?
思路:如果某个位置时1,则向右,是0则向下 (我们只需要找到比本行更多的1才有意义!)
19.A[i]是一个严格递增的整数数组,其中所有的数字都不相等,请设计一种算法,求出其中所有的A[i]=i的数字
思路:使用二分查找,如果mid=arr[mid],需要继续二分左边和右边,如果mid<arr[mid]继续二分左边,否则二分右边,将结果累加起来即可。
20.将 k 个有序数组合并为一个有序数组
思路:递归分治,后序遍历;先将左边归并,再将右边归并,在进行merge的方法。
class Solution(object):
def mergeKLists(self, lists):
"""
:type lists: List[ListNode]
:rtype: ListNode
"""
if(lists==None or lists==[]):
return None
elif(len(lists)==1):
return lists[0]
r = len(lists)-1
l = 0
mid = (l+r)/2
self.merge_sort(l,r,lists)
return lists[0]
def merge_sort(self,l,r,lists):
if(r==l):
return
else:
mid = (l+r)>>1
self.merge_sort(l,mid,lists)
self.merge_sort(mid+1,r,lists)
lists[l] = self.merge(lists[l],lists[mid+1])
def merge(self,l1,l2):
if(l1==None):
return l2
if(l2==None):
return l1
head = ListNode(0)
head.next = l1
pre = head
while(l1 and l2):
if(l1.val==l2.val):
tmp = l2
l2 = l2.next
tmp.next = l1.next
l1.next = tmp
pre = tmp
l1 = pre.next
elif(l1.val>l2.val):
tmp = l2
l2 = l2.next
pre.next = tmp
tmp.next = l1
pre = tmp
else:
pre = l1
l1 = l1.next
if(l2):
pre.next = l2
return head.next
21.给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。你找到的子数组应是最短的,请输出它的长度。
思路:clone原数组然后进行排序,使用头尾指针,如果值对应相等指针移动,两边都不相等就停止
22.给一个词典的集合,一组不重复字母,问这些字母可以组成几个单词
思路:先 Hash 一下字母,然后遍历这个词典,对于每个词语去 Hash 里面查一下,如果都有就能组成。
23.旋转数组;
无重复元素找最小值和无重复元素找目标值,
使用二分查找进行比较,只需要确定左边有序(mid>=R)还是右边有序(mid<R);
有重复元素单独进行处理:arr[L]=arr[m];相等,特殊处理,++L
class Solution:
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
if nums==[]:
return -1
n = len(nums)
self.target = target
#找到旋转数组的最小值
l = 0
r = n-1
min_pos = 0
while l<r:
mid = (l+r)//2
if r-l==1: //解决全部都是相同数值
min_pos = l if nums[l]<nums[r] else r
break
if nums[mid]<nums[mid+1] and nums[mid]<nums[mid-1]:
min_pos = mid
break
#左边不等于中间
if nums[r]<nums[mid]:
l=mid
#右边不等于中间
elif nums[r]>nums[mid]:
r= mid
elif nums[r]==nums[mid]:#不会有2 7 2这种情况在上面处理了
if nums[mid]>nums[l] or (nums[mid]==nums[l] and nums[mid]==nums[mid-1]):
l = mid
elif nums[mid]<nums[l] or (nums[mid]==nums[l] and nums[mid]==nums[mid+1]):
r = mid
#在两个有序数组里找target
num1 = self.bin_search(nums[:min_pos])
if num1!=-1:
return num1
num2 = self.bin_search(nums[min_pos:])
if num2!=-1:
return num2+min_pos
return -1
def bin_search(self,arr):
if arr==[]:
return -1
l = 0
r = len(arr)-1
#注意有等于
while l <= r:
mid = (l + r) // 2
if arr[mid] == self.target:
return mid
elif arr[mid] < self.target:
l = mid+1 #注意必须有
else:
r = mid-1 #注意必须有
return -1