一、排序算法

1.快速排序

(挖坑排序法经典讲解:https://blog.csdn.net/morewindows/article/details/6684558)

基本思想:

1.先从数列中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。
挖坑填数+分治法=快速排序算法

快排示例:

以一个数组作为示例,取区间第一个数为基准数。
image.png
初始时,i = 0; j = 9; X = a[i] = 72;//X就是取的基准
由于已经将a[0]中的数保存到X中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。从j开始向前找一个比X小的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++; 这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j—;

数组变为:
image.png i = 3; j = 7; X=72
再重复上面的步骤,先从后向前找,再从前向后找。
从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;
从i开始向后找,当i=5时,由于i==j退出。
此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。

数组变为:
image.png
可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。

对挖坑填数进行总结
1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
2.j—由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。
照着这个总结很容易实现挖坑填数的代码:

  1. public int AdjustArray(int s[], int l, int r){ //返回调整后基准数的位置
  2. int i = l, j = r;
  3. int x = s[i]; //s[l]即s[i]就是第一个坑
  4. while (i < j){
  5. while(i < j && s[j] >= x) // 从右向左找小于x的数来填s[i]
  6. j--;
  7. if(i < j){
  8. s[i] = s[j]; //将s[j]填到s[i]中,s[j]就形成了一个新的坑
  9. i++;
  10. }
  11. while(i < j && s[i] < x)// 从左向右找大于或等于x的数来填s[j]
  12. i++;
  13. if(i < j){
  14. s[j] = s[i]; //将s[i]填到s[j]中,s[i]就形成了一个新的坑
  15. j--;
  16. }
  17. }
  18. //退出时,i等于j。将x填到这个坑中。
  19. s[i] = x;
  20. return i;
  21. }

再写分治法的代码:

  1. void quick_sort(int s[], int l, int r){
  2. if (l < r){//l>=r则表示细分到了1个元素
  3. int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[]
  4. quick_sort(s, l, i - 1); // 递归调用
  5. quick_sort(s, i + 1, r);
  6. }
  7. }

这样的代码显然不够简洁,对其组合整理下:

  1. //快速排序
  2. private void quickSort(int[] data,int left,int right){
  3. if(left<right){//细分为每个数组只有一个值的情况了(left==right)
  4. int i=left;
  5. int j=right;
  6. int temp=data[i];//让最左边的为第一个坑,并保留这个位置上的原来的元素
  7. while (i<j){
  8. while(i<j && data[j]>=temp) j--;//找到从右往左第一个比坑小的
  9. if(i<j) {
  10. data[i]=data[j];
  11. i++;
  12. }
  13. while(i<j && data[i]<=temp) i++;//找到从左往右第一个比坑大的
  14. if(i<j){
  15. data[j]=data[i];
  16. j--;
  17. }
  18. }//跳出时为i==j,则把temp放进这个坑里(一个while只是一趟的把一个数组划分为两个分别大和小的数组)
  19. data[i]=temp;
  20. quickSort(data, left, i-1);
  21. quickSort(data, i+1, right);
  22. }
  23. }

快速排序还有很多改进版本,如随机选择基准数,区间内数据较少时直接用另的方法排序以减小递归深度

注1,有的书上是以中间的数作为基准数的,要实现这个方便非常方便,直接将中间的数和第一个数进行交换就可以了。

2.归并排序

基本思想:

归并示例:

递归分解+合并数列=归并排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。以下为合并两个有序数列:

  1. //将有序数组a[]和b[]合并到c[]中
  2. void MemeryArray(int a[], int n, int b[], int m, int c[]){
  3. int i, j, k;
  4. i = j = k = 0;
  5. while (i < n && j < m){
  6. if (a[i] < b[j])
  7. c[k++] = a[i++];//要么同时移动c和a
  8. else
  9. c[k++] = b[j++]; //要么同时移动c和b
  10. }
  11. while (i < n)
  12. c[k++] = a[i++];//将剩余的部分直接接到合并数组中
  13. while (j < m)
  14. c[k++] = b[j++];
  15. }

可以看出合并有序数列的效率是比较高的,可以达到O(n)。
解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?
可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。

  1. public static void main (String[] args){
  2. int a[]={1,5,9,8,9,4,2,34,2,3,56,723,0,34};//测试数据
  3. int[] temp = new int[a.length];
  4. mergesort(a, 0, a.length-1, temp);
  5. for(int num :a )
  6. System.out.print(num + " ");
  7. }
  8. //将有二个有序数列a[first...mid]和a[mid+1...last]合并
  9. void mergearray(int a[], int first, int mid, int last, int temp[]){
  10. int i = first, j = mid + 1;//两个数列的首指针下标
  11. int m = mid,n = last;s//两个数列的尾指针下标
  12. int k = 0;//合并后的数列的首指针下标
  13. while (i <= m && j <= n){
  14. if (a[i] <= a[j])
  15. temp[k++] = a[i++];
  16. else
  17. temp[k++] = a[j++];
  18. }
  19. while (i <= m)
  20. temp[k++] = a[i++];
  21. while (j <= n)
  22. temp[k++] = a[j++];
  23. for (i = 0; i < k; i++)
  24. a[first + i] = temp[i];//将排好序的数列替换掉原始数组的内容
  25. }
  26. //递归和合并数列
  27. void mergesort(int a[], int first, int last, int temp[]){
  28. if (first < last){//递归的终止条件:就是分出来的小组只有一个数据就停止
  29. int mid = (first + last) / 2;
  30. mergesort(a, first, mid, temp); //不断递归左边,最终得到左边有序
  31. mergesort(a, mid + 1, last, temp); //不断递归左边,最终得到右边有序
  32. if(a[mid]<a[mid+1]) return;//如果已经是有序,则不需要继续合并
  33. mergearray(a, first, mid, last, temp); //最后再将二个有序数列合并
  34. }
  35. //然后继续向上合并
  36. }

归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(NlogN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(NlogN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。
注:有的书上是在mergearray()合并有序数列时分配临时数组,但是过多的new操作会非常费时。因此作了下小小的变化。只在MergeSort()中new一个临时数组。后面的操作都共用这一个临时数组。

二、查找算法

1.二分查找

1.1经典二分问题

基本思想:

二分查找是一种基于比较目标值和数组中间元素的教科书式算法。针对元素有序的(升序)整型数组

  • 如果目标值等于中间元素,则找到目标值。
  • 如果目标值较小,证明目标值小于中间元素及右边的元素,继续在左侧搜索。
  • 如果目标值较大,证明目标值大于中间元素及左边的元素,继续在右侧搜索。

    算法代码描述:

    初始化指针 left = 0, right = n - 1。
    当 left <= right:比较中间元素 nums[pivot] 和目标值 target 。

  • 如果 target = nums[pivot],返回 pivot。

  • 如果 target < nums[pivot],则在左侧继续搜索 right = pivot - 1。
  • 如果 target > nums[pivot],则在右侧继续搜索 left = pivot + 1。

    经典二分问题:

    给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target 。写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。【存在就返回存在的那个下标】

    java代码实现:

    1. class Solution {
    2. public int search(int[] nums, int target) {
    3. int left = 0, right = nums.length - 1;//分别准备好左右端点
    4. while (left <= right) {//循环二分
    5. int pivot = left + (right - left) / 2;//取中点
    6. if (nums[pivot] == target) return pivot;//找到答案并返回
    7. if (target < nums[pivot]) right = pivot - 1;//向左继续找
    8. else left = pivot + 1;//向右继续找
    9. }
    10. return -1;//未找到,返回-1
    11. }
    12. }

    1.2 基础二分小变形1

    将目标数插入到第一个比目标值大的数左边
    给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
    思路:我们注意,这一题和上一题的区别在于,这一题并不存在‘没找到’这种情况,因为要插入到”第一个比目标值大的数“的左边,返回这样一个插入位置,你不可能”找不到“,不可返回-1。
    事实上,我们的目标也再不是找到一个相同的数字,而是找到第一个比目标值大的数,也就是插入位置。
    代码的内部逻辑是”一路找到最后,返回的一定是答案“,而上段代码的逻辑是”边找边判断,找到最后还没有找到,就返回-1“。

    代码实现

    1. public class Solution {
    2. public int searchInsert(int[] nums, int target) {
    3. int left = 0;
    4. while (left <= right) {
    5. int mid = (left + right) / 2;
    6. if (nums[mid] < target) {//此处注意不需要等号,加等号是加在最右边,不加等号是加在最左边
    7. left = mid + 1;
    8. } else {
    9. right = mid - 1;
    10. }
    11. }
    12. return left;
    13. }
    14. }

    提醒:所以,如果你是一个想注重细节,做到写二分代码不出错,那么你就需要关心最后返回值,while结束条件,while内部是否加判断等等这些细节了。

    1.3基础二分小变形2

    查找某个目标值的边界

    给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
    你的算法时间复杂度必须是 O(logn) 级别。
    如果数组中不存在目标值,返回 [-1, -1]。

  • 示例 1:输入: nums = [5,7,7,8,8,10], target = 8输出: [3,4]

  • 示例 2:输入: nums = [5,7,7,8,8,10], target = 6输出: [-1,-1]

思路:两个二分,稍微改一下,就可以找到第一个target或最后一个target;

  • 本元素和前面一个元素不相等,才代表找到了最左边的目标元素。
  • 本元素和后面一个元素不相等,才代表找到了最右边的目标元素。

    代码实现

    1. class Solution {
    2. public int[] searchRange(int[] nums, int target) {
    3. int[] ans=new int[2];
    4. ans[0]=searchRangeLeft(nums,target);
    5. ans[1]=searchRangeRight(nums,target);
    6. return ans;
    7. }
    8. public int searchRangeLeft(int[] nums, int target) {
    9. int left=0;
    10. int right=nums.length-1;
    11. while(left<=right){
    12. int mid=(left+right)/2;
    13. if(nums[mid]>target){//小于中间值,往左边查找
    14. right=mid-1;
    15. }else if(nums[mid]<target){//大于中间值,往右边查找
    16. left=mid+1;
    17. }else if(mid==0 || nums[mid-1]!=target){//mid==0是为了防止mid-1越界,同时也是右边界
    18. //中间值等于目标值,前一个值不等于目标值,查找到了最左边 或者 已经查找到最左边
    19. return mid;
    20. }else{
    21. right=mid-1;//中间值等于目标值,前一个值还等于目标值,继续向左缩小目标范围
    22. }
    23. }
    24. return -1;
    25. }
    26. public int searchRangeRight(int[] nums, int target) {
    27. int left=0;
    28. int right=nums.length-1;
    29. while(left<=right){
    30. int mid=(left+right)/2;
    31. if(nums[mid]>target){
    32. right=mid-1;
    33. }else if(nums[mid]<target){
    34. left=mid+1;
    35. }else if(mid==nums.length-1 || nums[mid+1]!=target){
    36. //mid==len-1是为了防止mid+1越界,同时也是左边界
    37. return mid;
    38. }else{
    39. left=mid+1;
    40. }
    41. }
    42. return -1;
    43. }
    44. }

    1.4泛化二分的概念

    与有序无序无关

    简单来说,我百度百科对二分的定义就是在顺序存储结构(数组)有序的情况下进行二分查找。
    从第四节开始,我们介绍的就不是传统意义上的二分查找了,不局限于”有序“,甚至不局限于线性结构,while循环里判断向左还是向右搜索的条件也不会这么单一,而更可能是这样:
    while (范围没有缩小为0) {
    if (满足某种条件) {
    排除一半答案
    } else {
    排除另一半答案
    }
    }
    我们的思想是:只要可以通过正确逻辑,用二分思想正确的缩小查找范围,都称之为二分。
    下面就来体会一下什么是二分思想:
    我们正在玩一个猜数字游戏。 游戏规则如下:我从 1 到 n 选择一个数字。 你需要猜我选择了哪个数字。每次你猜错了,我会告诉你这个数字是大了还是小了。
    你调用一个预先定义好的接口 guess(int num),它会返回 3 个可能的结果(-1,1 或 0):
    -1 : 我的数字比较小
    1 : 我的数字比较大
    0 : 恭喜!你猜对了!
    示例 :
    输入: n = 10, pick = 6输出: 6

    代码实现

    1. public class Solution extends GuessGame {
    2. public int guessNumber(int n) {
    3. int low = 1;
    4. int high = n;
    5. while (low <= high) {
    6. int mid = low + (high - low) / 2;
    7. int res = guess(mid);
    8. if (res == 0)
    9. return mid;
    10. else if (res < 0)
    11. high = mid - 1;
    12. else
    13. low = mid + 1;
    14. }
    15. return -1;
    16. }
    17. }

    这就是把条件抽象成一个接口,完全脱离了线性数据结构,更和有序无序没关系,只是二分的思想。

    1.5在线性结构上二分的题目积累

    例1:找峰值元素

    峰值元素是指其值大于左右相邻值的元素。给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
    示例 1:输入: nums = [1,2,3,1]输出: 2
    解释: 3 是峰值元素,你的函数应该返回其索引 2。
    示例 2:输入: nums = [1,2,1,3,5,6,4]输出: 1 或 5
    解释: 你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5, 其峰值元素为 6。
    说明:你的解法应该是 O(logN) 时间复杂度的。
    思路:可以用二分的要求:线性表能够根据中间元素的特点推测它两侧元素的性质,以达到缩减问题规模的效果即可,不一定非要有序。具体到本题来说,我们如何做到搜索到任何一个“峰值”呢?请看图:
    我的算法学习记录 - 图4
    要先有上升的趋势,后有下降的趋势。更通俗一点就是说,要保证前一个数字比峰值小,后一个数字比峰值大,我们只要每次搜索都满足这个条件,搜到最后就一定可以找到某个峰值,因为从递增到递减,肯定是因为中间有峰值的存在所导致的
    我的算法学习记录 - 图5
    我们看中点,如果中点向右有递增趋势,我们就继续搜索右边:
    我的算法学习记录 - 图6
    反之,有向左递增的趋势,我们就搜左边:
    我的算法学习记录 - 图7
    实现代码

    1. public class Solution {
    2. public int findPeakElement(int[] nums) {
    3. int l = 0, r = nums.length - 1;
    4. while (l < r) {
    5. int mid = (l + r) / 2;
    6. if (nums[mid] > nums[mid + 1])
    7. r = mid;
    8. else
    9. l = mid + 1;
    10. }
    11. return l;//最后缩小到l==r时,就找到了峰值
    12. }
    13. }

    我们发现,这个题目的代码逻辑就是第二节的”一路找到最后,返回的一定是答案“,不同于最基础的二分”边找边判断,找到最后还没有找到,就返回-1“。我们是缩小范围到最后,找到了答案l。

    例2:找原有序旋转数组的旋转后最小元素

    假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能为 [4,5,6,7,0,1,2] )。请找出其中最小的元素。你可以假设数组中不存在重复元素。
    示例 1:
    输入: [3,4,5,1,2]输出: 1
    示例 2:
    输入: [4,5,6,7,0,1,2]输出: 0
    分析:
    如果数组没有翻转,即 nums[left] <= nums[right],则 nums[left] 就是最小值,直接返回。
    如果数组翻转,需要找到数组中第二部分的第一个元素:
    我的算法学习记录 - 图8
    下面讨论数组翻转的情况下,如何收缩区间以找到这个元素:
    若 nums[left] <= nums[mid],说明区间 [left,mid] 连续递增,则最小元素一定不在这个区间里,可以直接排除。因此,令 left = mid+1,在 [mid+1,right] 继续查找。
    我的算法学习记录 - 图9
    否则,说明区间 [left,mid] 不连续,则最小元素一定在这个区间里。因此,令 right = mid,在 [left,mid] 继续查找[left,right] 表示当前搜索的区间。
    注意 right 更新时会被设为 mid 而不是 mid-1,因为 mid 无法被排除。
    代码实现

    1. class Solution {
    2. public int findMin(int[] nums) {
    3. if(nums.length==1) return nums[0];
    4. if(nums[0]<nums[nums.length-1]) return nums[0];
    5. int left=0;
    6. int right=nums.length-1;
    7. int mid=0;
    8. while(left<right){
    9. mid=(left+right)/2;
    10. if(nums[mid]>=nums[0]){
    11. left=mid+1;
    12. }else{
    13. right=mid;
    14. }
    15. }
    16. return nums[right];//最后缩小到了left==right,就找到了最小值
    17. }
    18. }

    例3:找原有序旋转数组是否存在某个目标值,并返回其索引

    其它条件和例2一样,例3要搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
    思路:先通过例2的方法找到分割点,再对左右某一边进行二分即可。
    解答:可以发现的是,我们将数组从中间分开成左右两部分的时候,一定有一部分的数组是有序的。拿示例[4,5,6,7,0,1,2]来看,我们从 6 这个位置分开以后数组变成了 [4, 5, 6] 和 [7, 0, 1, 2] 两个部分,其中左边 [4, 5, 6] 这个部分的数组是有序的,其他也是如此。这启示我们可以在常规二分查找的时候查看当前 mid 为分割位置分割出来的两个部分 [l, mid] 和 [mid + 1, r] 哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分查找的上下界,因为我们能够根据有序的那部分判断出 target 在不在这个部分:

  • 如果 [l, mid] 是有序数组,且 target 的大小满足 [nums[l],nums[mid-1]],则我们应该将搜索范围缩小至 [l, mid - 1],否则在 [mid + 1, r] 中寻找。

  • 如果 [mid, r] 是有序数组,且 target 的大小满足 [nums[mid+1],nums[r]],则我们应该将搜索范围缩小至 [mid + 1, r],否则在 [l, mid - 1] 中寻找。

代码实现

  1. public static int search(int[] nums, int target) {
  2. int n = nums.length;
  3. if (n == 0) {
  4. return -1;
  5. }
  6. if (n == 1) {
  7. return nums[0] == target ? 0 : -1;
  8. }
  9. int l = 0, r = n - 1;
  10. while (l <= r) {
  11. int mid = (l + r) / 2;
  12. if (nums[mid] == target) {//相等的时候返回
  13. return mid;
  14. }
  15. if (nums[0] <= nums[mid]) {//
  16. if (nums[0] <= target && target < nums[mid]) {//
  17. r = mid - 1;
  18. } else {
  19. l = mid + 1;
  20. }
  21. } else {//
  22. if (nums[mid] < target && target <= nums[n - 1]) {//
  23. l = mid + 1;
  24. } else {
  25. r = mid - 1;
  26. }
  27. }
  28. }
  29. return -1;
  30. }

1.6二维数组的二分查找

判断 m x n 矩阵中,是否存在一个目标值

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 1:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
输出: true
示例 2:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 13
输出: false
仔细观察我们发现,其实这个二维数组整体就是有序的,所以当成一维数组二分查找即可,但是要注意二维数组的操作,需要一点功底。
代码实现

  1. class Solution {
  2. public boolean searchMatrix(int[][] matrix, int target) {
  3. if (matrix == null || matrix.length == 0) {
  4. return false;
  5. }
  6. int row = matrix.length;
  7. int col = matrix[0].length;
  8. int start = 0;
  9. int end = row * col - 1;
  10. while (start <= end) {
  11. int mid = start + (end - start) / 2;//正规的二分写法,避免溢出
  12. if (matrix[mid / col][mid % col] == target)
  13. return true;
  14. else if (matrix[mid / col][mid % col] > target)
  15. end = mid - 1;
  16. else
  17. start = mid + 1;
  18. }
  19. return false;
  20. }
  21. }

1.7二叉树上二分的题目积累

我们刚才学会了一些一维二维数组的二分操作,下面我们再去其它数据结构试试看,继续养成二分的思想。

例1:先看一道简单的在二叉树上查找值。

给定一个不为空的二叉搜索树和一个目标值 target,请在该二叉搜索树中找到最接近目标值 target 的数值。
注意:给定的目标值 target 是一个浮点数,题目保证在该二叉搜索树中只会存在一个最接近目标值的数
示例:
输入: root = [4,2,5,1,3],目标值 target = 3.714286
4
/ \
2 5
/ \
1 3
输出: 4
思路:二分,当前节点比target大就往左边搜(因为右边的差距更大),当前节点比target小就往右搜(因为左边的差距更大)。
补充:二叉查找树它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树
代码实现

  1. public class MyTest {
  2. public static void main(String[] args) {
  3. TreeNode node1=new TreeNode(4);
  4. TreeNode node2=new TreeNode(2);
  5. node1.left=node2;
  6. node1.right=new TreeNode(5);
  7. node2.left=new TreeNode(1);
  8. node2.right=new TreeNode(3);
  9. int closest=closestSearch(node1,2.714286);
  10. System.out.println(closest);
  11. }
  12. private static int closestSearch(TreeNode root,double target){
  13. int val=root.val;
  14. int closest=root.val;//closest最近的
  15. while(root!=null){
  16. val=root.val;
  17. closest=Math.abs(val-target)<Math.abs(closest-target)?val:closest;//上次的差值和这次的比较
  18. root=root.val>target?root.left:root.right;
  19. }
  20. return closest;
  21. }
  22. }
  23. class TreeNode {
  24. int val;
  25. TreeNode left;
  26. TreeNode right;
  27. TreeNode(int x){
  28. val = x;
  29. }
  30. }

例2:给出一个完全二叉树,求出该树的节点个数。

完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
示例:
输入:
1
/ \
2 3
/ \ /
4 5 6
输出: 6
思路:如果不考虑最后一层,节点数完全可以算出来,每一层分别为1,2,4,8….你会发现是2的n次方,那么总和也很好求,所以问题就在于如何知道最后一层有多少节点,换句话说,最后一层的最右边的那个节点在哪里?我们需要二分搜索。
我的算法学习记录 - 图10
目标:找箭头指向的结点。
我们采用二分法:
1)找到右子树的最左结点
我的算法学习记录 - 图11

我们知道总深度为4,如果右子树深度为3,说明我的算法学习记录 - 图12图中最后的1是存在的(说明最后一行最右结点一定来自右子树),否则,如果我们修改一下如下图:
我的算法学习记录 - 图13
右子树深度为2!=4-1,不存在最后一行的结点。(说明最后一行最右结点一定来自左子树).
2)判断之后,如果是这种情况,我们排除了左子树,计算排除的结点个数,并对右子树(如图2)做相同的处理。
我的算法学习记录 - 图14
我的算法学习记录 - 图15

更新结点数(未被框起的部分,满二叉树公式+1)+1是根结点
3)对方框内重复此过程。
我的算法学习记录 - 图16
我们继续看右子树,发现右子树深度为1!=3-1.
说明最深层最右结点来自于左子树。所以对左子树重复上述过程
我的算法学习记录 - 图17
我们发现,右子树深度=1=2(整棵树深度)-1,说明最深层最右结点来自于右子树,所以对右子树重复此过程。我的算法学习记录 - 图18最终找到它。
代码实现

  1. public class TestStaticClass {
  2. public static class Node {
  3. public int value;
  4. public Node left;
  5. public Node right;
  6. public Node(int data) {
  7. this.value = data;
  8. }
  9. }
  10. //返回结点个数
  11. public static int nodeNum(Node head) {
  12. if (head == null) {
  13. return 0;
  14. }
  15. return bs(head, 1, mostLeftLevel(head, 1));
  16. }
  17. //返回根为node,当前层数为l,总深度为h的结点个数
  18. public static int bs(Node node, int l, int h) {
  19. if (l == h) {
  20. return 1;//到达最后一层
  21. }
  22. if (mostLeftLevel(node.right, l + 1) == h) { //右子树最深一行最左不为空
  23. return (1 << (h - l)) + bs(node.right, l + 1, h); //右bs+左子树结点个数
  24. } else { //右子树最深一行最左为空
  25. return (1 << (h - l - 1)) + bs(node.left, l + 1, h);//左bs+右子树结点个数
  26. }
  27. }
  28. //计算树的高度
  29. public static int mostLeftLevel(Node node, int level) {
  30. while (node != null) {
  31. level++;
  32. node = node.left;
  33. }
  34. return level - 1;
  35. }
  36. public static void main(String[] args) {
  37. Node head = new Node(1);
  38. head.left = new Node(2);
  39. head.right = new Node(3);
  40. head.left.left = new Node(4);
  41. head.left.right = new Node(5);
  42. head.right.left = new Node(6);
  43. System.out.println(nodeNum(head));
  44. }
  45. }

补充:
1.满二叉树公式为 (2^k) -1 【k是树的层数】
2.关于使用static关键字修饰类:
java里面static一般用来修饰成员变量或函数。但有一种特殊用法是用static修饰内部类,普通类是不允许声明为静态的,只有内部类才可以。被static修饰的内部类可以直接作为一个普通类来使用,而不需实例一个外部类(见如下代码):需要注意的是当一个内部类没有使用static修饰的时候,是不能直接使用内部类创建对象,须要先使用外部类对象点new内部类对象及(外部类对象.new 内部类())

  1. 1、被static修饰的内部类可以直接作为一个普通类来使用,而不需实例一个外部类
  2. class OuterClass {
  3. public static class InnerClass{
  4. public InnerClass(){
  5. System.out.println("============= 我是一个内部类'InnerClass' =============");
  6. }
  7. public void test(){
  8. }
  9. }
  10. }
  11. public class TestStaticClass {
  12. public static void main(String[] args) {
  13. new OuterClass.InnerClass();// 不需要new一个InnerClas
  14. new OuterClass.InnerClass().test();
  15. }
  16. }
  17. 2、如果没有用static修饰InterClass,则只能按如下方式调用:需要先new 一个外部类实例
  18. OuterClass oc = new OuterClass(); 在使用外部类实例点内部类实例
  19. oc.new InnerClass();
  20. class OuterClass {
  21. public class InnerClass{
  22. public InnerClass(){
  23. System.out.println("============= 我是一个内部类'InnerClass' =============");
  24. }
  25. public void test(){
  26. }
  27. }
  28. }
  29. public class TestStaticClass {
  30. public static void main(String[] args) {
  31. new OuterClass().new InnerClass();// OutClass需要先生成一个实例,然后再new一个InnerClass();
  32. new OuterClass().new InnerClass().test();
  33. }
  34. }

1..8数学中的二分

实现 int sqrt(int x) 函数。计算并返回 x 的平方根,其中 x 是非负整数。由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4输出: 2
示例 2:
输入: 8输出: 2
说明: 8 的平方根是 2.82842…,
由于返回类型是整数,小数部分将被舍去。
思路依旧是二分,格式和上文都一样。
代码实现

  1. public class MyTest {
  2. public static void main(String[] args) {
  3. int i = mySqrt(8);
  4. System.out.println(i);
  5. }
  6. public static int mySqrt(int x) {
  7. long left = 0;
  8. long right = Integer.MAX_VALUE;// MAX_VALUE=0x7fffffff;
  9. while (left < right) {
  10. // >>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位仍补0
  11. long mid = (left + right + 1) >>> 1;
  12. long square = mid * mid;
  13. if (square > x) {
  14. right = mid - 1;
  15. } else {
  16. left = mid;
  17. }
  18. }
  19. return (int) left;//left==right时跳出循环
  20. }
  21. }

1.9最大化或最小化问题

如果在求解最大最小问题中,能比较简单的判断某个解是否满足条件(这里的简单一般指的是o(n)及以下,视具体数据范围而定),使用二分搜索答案就能很好的解决问题。
题目链接:http://poj.org/problem?id=1064
题目大意:有n条绳子,长度分别为L[i]。如果从他们中切割出k条长度相同的绳子的话,这k条绳子每条最长能有多长?(答案保留小数点后两位,规定1单位长度的绳子最多可以切割成100份)。
思路:二分搜索答案,每条最短0,最大设置一个较大的数,然后开始二分答案并依次判断,判断也很简单,判断每个绳子的长度整除答案的累加和是不是大于k就好了。
代码实现
补充:有n个牛栏,选m个放进牛,相当于一条线段上有 n 个点,选取 m 个点,使得相邻点之间的最小距离值最大
思路:和上一道题类似,二分答案,判断答案也很简单,贪心即可,遍历,遇到大于枚举的距离就放一只牛,看最后能不能放得下。
代码实现

三、回溯、贪心和动态规划

回溯、贪心和动态规划三类算法思想是层层递进,环环相扣的,如果想学习好的话,就应该放在一起系统的学习,否则,你很难学会他们
如果你能系统的完成下面的 36 道算法面试题的话,那么对于回溯、贪心和动态规划的问题,就会很容易的解答了:
回溯算法思想

  1. leetcode 112 号算法题:路径总和
  2. leetcode 113 号算法题:路径总和 II
  3. leetcode 46 号算法题:全排列
  4. leetcode 47 号算法题:全排列 II
  5. leetcode 77 号算法题:组合
  6. leetcode 39 号算法题:组合总和
  7. leetcode 40 号算法题:组合总和 Ⅱ
  8. leetcode 216 号算法题:组合总和 Ⅲ
  9. leetcode 78 号算法题:子集
  10. leetcode 90 号算法题:子集Ⅱ
  11. leetcode 17 号算法题:电话号码的字母组合
  12. leetcode 93 号算法题:复原 IP 地址
  13. leetcode 22 号算法题:括号生成
  14. leetcode 51 号算法题:N 皇后
  15. leetcode 37 号算法题:数独问题

贪心算法思想

  1. leetcode 455 号算法题:分发饼干
  2. leetcode 322 号算法题:零钱兑换
  3. leetcode 45 号算法题:跳跃游戏 Ⅱ
  4. leetcode 1578 号算法题:避免重复字母的最小删除成本
  5. leetcode 402 号算法题:移掉K位数字

动态规划算法思想

  1. leetcode 509 号算法题:斐波那契数
  2. leetcode 322 号算法题:零钱兑换
  3. leetcode 64 号算法题:最小路径和
  4. leetcode 53 号算法题:最大子数组之和
  5. leetcode 647 号算法题:回文子串
  6. leetcode 5 号算法题:最长回文串
  7. leetcode 131 号算法题:分割回文串
  8. leetcode 516 号算法题:最长回文子序列
  9. leetcode 300 号算法题:最长上升子序列
  10. leetcode 1143 号算法题:最长公共子序列
  11. leetcode 72 号算法题:编辑距离
  12. leetcode 44 号算法题:通配符匹配
  13. leetcode 10 号算法题:正则表达式匹配
  14. leetcode 486 号算法题:预测赢家
  15. leetcode 877 号算法题:石子游戏
  16. 0-1 背包问题

你可以按照上面的顺序来刷这些题目,注意,一定要按照 回溯,然后 贪心,最后才 动态规划 的顺序来刷题,我相信你能系统的学会回溯、贪心和动态规划
题解

回溯算法思想

leetcode 112 号算法题:路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。
叶子节点 是指没有子节点的节点。
示例 1:
我的算法学习记录 - 图19
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true

  1. //深度优先搜索
  2. class Solution {
  3. //只有深度遍历的每一层都返回true才会最终返回true(从最深处逐级往上)
  4. public boolean hasPathSum(TreeNode root, int targetSum) {
  5. if(root==null)//如果这个成立,说明是像题干中的4的左节点一样的节点,4并不是叶子节点,防止没到叶子节点但累计和到了目标和的情况
  6. return false;
  7. if(root.left==null&&root.right==null)//只有是叶子节点
  8. return targetSum==root.val;
  9. return hasPathSum(root.left,targetSum-root.val)||
  10. hasPathSum(root.right,targetSum-root.val);
  11. }
  12. }
  13. class Solution {
  14. public boolean hasPathSum(TreeNode root, int targetSum) {
  15. return dfs(root, targetSum, 0);
  16. }
  17. private boolean dfs(TreeNode node,int targetSum,int nodeValSum){
  18. if(node==null)
  19. return false;
  20. if(node.left==null&&node.right==null)
  21. return (nodeValSum+node.val)==targetSum;
  22. return dfs(node.left,targetSum,nodeValSum+node.val)||dfs(node.right,targetSum,nodeValSum+node.val);
  23. }
  24. }
  25. //广度优先搜索
  26. class Solution {
  27. public boolean hasPathSum(TreeNode root, int targetSum) {
  28. if(root==null) return false;
  29. LinkedList<TreeNode> nodeQue=new LinkedList<>();
  30. LinkedList<Integer> valQue=new LinkedList<>();
  31. nodeQue.addLast(root);
  32. valQue.addLast(root.val);
  33. while (!nodeQue.isEmpty()){
  34. TreeNode node=nodeQue.pollFirst();
  35. Integer value = valQue.pollFirst();
  36. if(node.left==null&&node.right==null){//才是叶子节点
  37. if(value==targetSum)
  38. return true;
  39. continue;//当前节点都到叶子节点了,就不往下走了,而是继续下一个节点的判断
  40. }
  41. if(node.left!=null){
  42. nodeQue.addLast(node.left);
  43. valQue.addLast(node.left.val+value);
  44. }
  45. if(node.right!=null){
  46. nodeQue.addLast(node.right);
  47. valQue.addLast(node.right.val+value);
  48. }
  49. }
  50. return false;
  51. }
  52. }

leetcode 113 号算法题:路径总和 II

  1. //深度优先
  2. class Solution {
  3. public List<List<Integer>> pathSum(TreeNode root,int targetSum) {
  4. List<List<Integer>> res=new ArrayList<>();
  5. if(root==null) return res;
  6. LinkedList<Integer> path=new LinkedList<>();
  7. dfs(root, targetSum,res, path);
  8. return res;
  9. }
  10. private void dfs(TreeNode node,int targetSum,List<List<Integer>> res, LinkedList<Integer> path){
  11. if(node==null) return;
  12. path.addLast(node.val);
  13. if(node.left==null&&node.right==null&&targetSum==node.val)
  14. res.add(new LinkedList<>(path));
  15. dfs(node.left,targetSum-node.val,res,path);
  16. dfs(node.right,targetSum-node.val,res,path);
  17. path.removeLast();
  18. }
  19. }
  20. //广度优先
  21. public List<List<Integer>> pathSum(TreeNode root, int sum) {
  22. List<List<Integer>> list = new ArrayList<>();
  23. if (root == null) return list;
  24. List<Integer> path = new ArrayList<>();
  25. Stack<TreeNode> s = new Stack<>();
  26. int pathSum = 0;
  27. TreeNode lastVisited = null;
  28. TreeNode curr = root;
  29. while (curr != null || !s.isEmpty()){
  30. //把每个节点都当做根节点处理,并且把这个根节点的所有子节点都加入到栈中
  31. //所有节点入栈都从这里添加,一条路径所有节点的累加和都在这里进行
  32. while (curr != null){// 1. 遍历搜索左边的节点,一直到叶子节点
  33. s.push(curr);// 将节点压入栈中
  34. path.add(curr.val);// 将当前的节点加入当前的路径中
  35. pathSum += curr.val;// 累计计算当前 path 的总和
  36. curr = curr.left;
  37. }
  38. // 2. 访问左节点的右子树:先拿到左节点,这里使用 peek() 的原因:如果这个左节点有右子树的话,我们就不用再一次 push 到栈中
  39. curr = s.peek();
  40. if (curr.right != null && curr.right != lastVisited){// 条件:有右子树,并且右子树还没有访问过
  41. curr = curr.right;// 访问右子树
  42. continue; // 跳出外面的循环,来处理当前左节点的右子树
  43. //这里是为了把所有节点都当做根节点处理,即把当前节点放到while (curr!=null){这个循环中去处理
  44. }
  45. //能走到这里的路径一定是一条从根节点到叶子节点的路径,从这以下的代码都是对叶子节点的操作
  46. // 3. 检查是否符合路径和的条件:节点的左右子节点都为 null,并且路径总和等于目标值 sum
  47. if (curr.left == null && curr.right == null && pathSum == sum){// 当前路径符合条件,加入到返回结果中
  48. list.add(new ArrayList<Integer>(path));
  49. }
  50. s.pop();// 出栈当前的节点,因为已经处理完了
  51. lastVisited = curr; // 标记当前节点已经被访问,免得在这里被重复访问if (curr.right != null
  52. pathSum -= curr.val; // 当前路径总和减去当前的节点值
  53. path.remove(path.size() - 1);// 当前路径中删除掉最后一个节点(也就是当前节点)
  54. curr = null; // 将当前的节点设置为 null,这是为了下次循环的时候从栈中拿到节点来进行处理,也是防止再次进去 while (curr != null){这个循环中
  55. }
  56. return list;
  57. }

leetcode 46 号算法题:全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
题解:
image.png

  1. //过程1:可以选取重复数字的排列
  2. public List<List<Integer>> permute(int[] nums) {
  3. List<List<Integer>> res = new ArrayList<>();
  4. List<Integer> path = new ArrayList<>();
  5. dfs(nums, -1, path, res);
  6. return res;
  7. }
  8. private void dfs(int[] nums, int index,List<Integer> path,List<List<Integer>> res) {
  9. if (path.size() == nums.length) {
  10. return;
  11. }
  12. if (index != -1) path.add(nums[index]);
  13. if (path.size() == nums.length) {
  14. res.add(new ArrayList<>(path));
  15. }
  16. for(int i=0;i<nums.length;i++){
  17. dfs(nums, i, path, res);
  18. }
  19. if (index != -1) path.remove(path.size() - 1); // 回溯的过程中,将当前的节点从 path 中删除
  20. }
  21. //过程2:合并回溯条件和添加排列(但是每种排列都被重复添加了多次)
  22. public List<List<Integer>> permute(int[] nums) {
  23. List<List<Integer>> res = new ArrayList<>();
  24. List<Integer> path = new ArrayList<>();
  25. dfs(nums, -1, path, res);
  26. return res;
  27. }
  28. private void dfs(int[] nums, int index,List<Integer> path,List<List<Integer>> res) {
  29. if (path.size() == nums.length) {
  30. res.add(new ArrayList<>(path));
  31. return;
  32. }
  33. if (index != -1) path.add(nums[index]);
  34. for(int i=0;i<nums.length;i++){
  35. dfs(nums, i, path, res);
  36. }
  37. if (index != -1) path.remove(path.size() - 1);// 回溯的过程中,将当前的节点从 path 中删除
  38. }
  39. //过程3:去除过程2中的重复,回到过程1中的可以选取重复数字的排列,总结出回溯算法框架代码
  40. public List<List<Integer>> permute(int[] nums) {
  41. List<List<Integer>> res = new ArrayList<>();
  42. List<Integer> path = new ArrayList<>();
  43. dfs(nums, -1, path, res);
  44. return res;
  45. }
  46. private void dfs(int[] nums, int index,List<Integer> path,List<List<Integer>> res) {
  47. if (path.size() == nums.length) {
  48. res.add(new ArrayList<>(path));
  49. return;
  50. }
  51. for(int i=0;i<nums.length;i++){
  52. path.add(nums[i]);//添加当前节点
  53. dfs(nums, i, path, res);
  54. path.remove(path.size() - 1); // 回溯的过程中,将当前的节点从 path 中删除
  55. }
  56. }
  57. 过程4:找到全排列组合(利用contains方法来进行判断,其中,index也是不需要的方法参数,可以去掉)
  58. public List<List<Integer>> permute(int[] nums) {
  59. List<List<Integer>> res = new ArrayList<>();
  60. List<Integer> path = new ArrayList<>();
  61. dfs(nums, path, res);
  62. return res;
  63. }
  64. private void dfs(int[] nums,List<Integer> path,List<List<Integer>> res) {
  65. if (path.size() == nums.length) {
  66. res.add(new ArrayList<>(path));
  67. return;
  68. }
  69. for(int i=0;i<nums.length;i++){//这个循环时间复杂度为O(N^2),for循环O(n),contains()方法O(n)
  70. if(path.contains(nums[i])) continue;
  71. path.add(nums[i]);//添加当前节点
  72. dfs(nums, path, res);
  73. path.remove(path.size() - 1); // 回溯的过程中,将当前的节点从 path 中删除
  74. }
  75. }
  76. //过程5:从过程4的时间复杂度O(N!*N^2)优化到了O(N!*N)
  77. public List<List<Integer>> permute(int[] nums) {
  78. List<List<Integer>> res = new ArrayList<>();
  79. List<Integer> path = new ArrayList<>();
  80. boolean[] used=new boolean[nums.length];
  81. dfs(nums, path, res,used);
  82. return res;
  83. }
  84. private void dfs(int[] nums,List<Integer> path,List<List<Integer>> res,boolean[] used) {
  85. if (path.size() == nums.length) {
  86. res.add(new ArrayList<>(path));
  87. return;
  88. }
  89. for(int i=0;i<nums.length;i++){//这个循环时间复杂度为O(N),for循环O(n),判断为O(1)
  90. if(used[i]) continue;
  91. path.add(nums[i]);//添加当前节点
  92. used[i]=true;
  93. dfs(nums, path, res,used);
  94. path.remove(path.size() - 1); // 回溯的过程中,将当前的节点从 path 中删除
  95. used[i]=false;
  96. }
  97. }

leetcode 47 号算法题:全排列 II

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

  1. //整体思路用一句话表示,两数相同时,前面的数据没插入,后面数据也不会插入
  2. 第二条由以下条件进行判断,两数相同时(nums[i] == nums[i - 1]),当前一个数没有递归时(!vis[i - 1]),则当前数据不递归(continue),这条规则保证了递归过程中,相同数据加入递归的顺序始终为坐标位置的顺序,即相同数据不排序(当然第二条的前提是数组必须排序,使相同的数据相邻)
  3. class Solution {
  4. public List<List<Integer>> permuteUnique(int[] nums) {
  5. List<List<Integer>> res=new ArrayList<>();
  6. List<Integer> path=new ArrayList<>();
  7. boolean[] used=new boolean[nums.length];
  8. Arrays.sort(nums);
  9. dfs(res,path,nums,used);
  10. return res;
  11. }
  12. private void dfs(List<List<Integer>> res,List<Integer> path,int[] nums,boolean[] used){
  13. if(path.size()==nums.length){
  14. res.add(new ArrayList<>(path));
  15. return;
  16. }
  17. for(int i=0;i<nums.length;i++){
  18. //当前元素被用过了就跳过
  19. //走到后面这个判断条件一定是used[i]==false,即当前元素没有被用过,且
  20. if(used[i] || (i>0 && nums[i]==nums[i-1]&&!used[i-1]))
  21. continue;
  22. path.add(nums[i]);
  23. used[i]=true;
  24. dfs(res,path,nums,used);
  25. path.remove(path.size()-1);
  26. used[i]=false;
  27. }
  28. }
  29. }
  30. class Solution {
  31. public List<List<Integer>> permuteUnique(int[] nums) {
  32. List<List<Integer>> res=new ArrayList<>();
  33. List<Integer> path=new ArrayList<>();
  34. boolean[] used=new boolean[nums.length];
  35. Arrays.sort(nums);
  36. dfs(res,path,nums,used);
  37. return res;
  38. }
  39. private void dfs(List<List<Integer>> res,List<Integer> path,int[] nums,boolean[] used){
  40. if(path.size()==nums.length){
  41. res.add(new ArrayList<>(path));
  42. return;
  43. }
  44. int prev=Integer.MAX_VALUE;
  45. for(int i=0;i<nums.length;i++){
  46. if(used[i] || prev==nums[i])
  47. continue;
  48. path.add(nums[i]);
  49. used[i]=true;
  50. dfs(res,path,nums,used);
  51. path.remove(path.size()-1);
  52. used[i]=false;
  53. prev=nums[i];
  54. }
  55. }
  56. }

leetcode 77 号算法题:组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

  1. class Solution {
  2. public List<List<Integer>> combine(int n, int k) {
  3. List<List<Integer>> res=new ArrayList<>();
  4. List<Integer> path=new ArrayList<>();
  5. dfs(n,k,path,res);
  6. return res;
  7. }
  8. private void dfs(int n,int k, List<Integer> path,List<List<Integer>> res){
  9. if(path.size()==k){
  10. res.add(new ArrayList<>(path));
  11. return;
  12. }
  13. for(int i=1;i<=n;i++){
  14. if(path.size()>0&&path.get(path.size()-1)>=i)
  15. continue;
  16. path.add(i);
  17. dfs(n, k, path, res);
  18. path.remove(path.size()-1);
  19. }
  20. }
  21. }

leetcode 39 号算法题:组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
题解:
在当前的函数中,每次我们可以选择跳过不用第 index 个数,即执行 dfs(target, path, index + 1)。
也可以选择使用第 index 个数,即执行 dfs(target - candidates[index ], path, index ),注意到每个数字可以被无限制重复选取,因此搜索的下标仍为 index。

  1. class Solution {
  2. public List<List<Integer>> combinationSum(int[] candidates, int target) {
  3. List<List<Integer>> res=new ArrayList<>();
  4. List<Integer> path=new ArrayList<>();
  5. dfs(candidates, target, res, path, 0);
  6. return res;
  7. }
  8. public void dfs(int[] candidates,int target,List<List<Integer>> res,List<Integer> path,int index) {
  9. if (index==candidates.length) {
  10. return;
  11. }
  12. if (target==0) {
  13. res.add(new ArrayList<>(path));
  14. return;
  15. }
  16. dfs(candidates,target,res,path,index+1);// 直接跳过
  17. if (target-candidates[index]>=0) {// 选择当前数
  18. path.add(candidates[index]);
  19. dfs(candidates,target-candidates[index],res,path,index);
  20. path.remove(path.size() - 1);
  21. }//此处是target <= 0时,就自然而然的结束了这个方法,所以不用加在递归条件里面
  22. }
  23. }
  24. class Solution {
  25. public List<List<Integer>> combinationSum(int[] candidates, int target) {
  26. List<List<Integer>> res=new ArrayList<>();
  27. List<Integer> path=new ArrayList<>();
  28. dfs2(candidates,target,0,path,res,0);
  29. return res;
  30. }
  31. private void dfs2(int[] candidates, int target,int pathSum,List<Integer> path,List<List<Integer>> res,int index){
  32. if(index==candidates.length||pathSum>target)
  33. return;
  34. if(target==pathSum){
  35. res.add(new ArrayList<>(path));
  36. return;
  37. }
  38. dfs2(candidates, target, pathSum, path, res, index+1);
  39. if(pathSum<target){
  40. path.add(candidates[index]);
  41. dfs2(candidates, target, pathSum+candidates[index], path, res, index);
  42. path.remove(path.size()-1);
  43. }
  44. }
  45. }

leetcode 40 号算法题:组合总和 Ⅱ

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]

  1. public List<List<Integer>> combinationSum2(int[] candidates, int target) {
  2. List<List<Integer>> res=new ArrayList<>();
  3. LinkedList<Integer> path=new LinkedList<>();
  4. Arrays.sort(candidates);// 排序方便进行剪枝
  5. dfs(res,path,candidates,0,target);
  6. return res;
  7. }
  8. private void dfs(List<List<Integer>> res,Deque<Integer> path,int[] candidates,int index,int target){
  9. if (target==0){
  10. res.add(new ArrayList<>(path));
  11. return;
  12. }
  13. for (int i=index;i<candidates.length;i++){
  14. // 大剪枝:减去 candidates[i] 小于 0,减去后面的 candidates[i + 1]、candidates[i + 2] 肯定也小于 0,因此用 break
  15. if (target-candidates[i]<0) break;
  16. // 小剪枝:同一层相同数值的结点,从第 2 个开始,候选数更少,结果一定发生重复,因此跳过,用 continue
  17. if (i > index && candidates[i] == candidates[i - 1]) {
  18. continue;
  19. }
  20. path.addLast(candidates[i]);
  21. dfs(res,path,candidates,i+1,target-candidates[i]);
  22. path.removeLast();
  23. }
  24. }
  25. class Solution {
  26. public List<List<Integer>> combinationSum2(int[] candidates, int target) {
  27. List<List<Integer>> res=new ArrayList<>();
  28. LinkedList<Integer> path=new LinkedList<>();
  29. Arrays.sort(candidates);// 排序方便进行剪枝
  30. dfs(res,path,candidates,0,target);
  31. return res;
  32. }
  33. private void dfs(List<List<Integer>> res,Deque<Integer> path,int[] nums,int index,int target){
  34. if (index>nums.length) return;
  35. if (target==0){
  36. res.add(new ArrayList<>(path));
  37. return;
  38. }
  39. List<Integer> pre=new ArrayList<>(nums.length);
  40. for (int i=index;i<nums.length;i++){// 此处遍历决定的是每一个位置的数字的值
  41. if (pre.contains(nums[i])) continue;
  42. if (target-nums[i]<0) break; // 剪枝
  43. pre.add(nums[i]);
  44. path.addLast(nums[i]);
  45. dfs(res,path,nums,i+1,target-nums[i]);
  46. path.removeLast();
  47. }
  48. }
  49. }

leetcode 216 号算法题:组合总和 Ⅲ

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

  1. class Solution {
  2. public List<List<Integer>> combinationSum3(int k, int n) {
  3. List<List<Integer>> res=new ArrayList<>();
  4. List<Integer> path=new ArrayList<>();
  5. dfs(n,k,path,res);
  6. return res;
  7. }
  8. private void dfs(int n, int k, List<Integer> path, List<List<Integer>> res) {
  9. if(path.size()>k||n<0){
  10. return;
  11. }
  12. if(path.size()==k&&n==0){
  13. res.add(new ArrayList<>(path));
  14. return;
  15. }
  16. for(int i=1;i<=9;i++){
  17. if(path.size()>0&&path.get(path.size()-1)>=i)
  18. continue;
  19. path.add(i);
  20. dfs(n-i, k, path, res);
  21. path.remove(path.size()-1);
  22. }
  23. }
  24. }

leetcode 78 号算法题:子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

  1. class Solution {
  2. public List<List<Integer>> subsets(int[] nums) {
  3. List<List<Integer>> res=new ArrayList<>();
  4. List<Integer> path=new ArrayList<>();
  5. Arrays.sort(nums);
  6. dfs(nums,0,path,res);
  7. return res;
  8. }
  9. private void dfs(int[] nums, int index, List<Integer> path, List<List<Integer>> res) {
  10. if(index==nums.length){
  11. res.add(new ArrayList<>(path));
  12. return;
  13. }
  14. dfs(nums, index+1, path, res);
  15. path.add(nums[index]);
  16. dfs(nums, index+1, path, res);
  17. path.remove(path.size()-1);
  18. }
  19. }
  20. class Solution {
  21. public List<List<Integer>> subsets(int[] nums) {
  22. List<List<Integer>> res=new ArrayList<>();
  23. List<Integer> path=new ArrayList<>();
  24. Arrays.sort(nums);
  25. dfs(nums,0,path,res);
  26. return res;
  27. }
  28. private void dfs(int[] nums, int index,List<Integer> path, List<List<Integer>> res) {
  29. if(path.size()<=nums.length){
  30. res.add(new ArrayList<>(path));
  31. }
  32. for(int i=0;i<nums.length;i++){
  33. if(path.size()>0&&path.get(path.size()-1)>=nums[i]) continue;
  34. path.add(nums[i]);
  35. dfs(nums, index+1,path, res);
  36. path.remove(path.size()-1);
  37. }
  38. }
  39. }
  40. class Solution {
  41. List<Integer> path = new ArrayList<>();
  42. List<List<Integer>> res = new ArrayList<>();
  43. public List<List<Integer>> subsets(int[] nums) {
  44. int n = nums.length;
  45. for (int mask = 0; mask < (1 << n); ++mask) {
  46. path.clear();
  47. for (int i = 0; i < n; ++i) {//这里n是固定的
  48. if ((mask & (1 << i)) != 0) {
  49. path.add(nums[i]);
  50. }
  51. }
  52. res.add(new ArrayList<>(path));
  53. }
  54. return res;
  55. }
  56. }

leetcode 90 号算法题:子集Ⅱ

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

  1. class Solution {
  2. public List<List<Integer>> subsetsWithDup(int[] nums) {
  3. List<List<Integer>> res=new ArrayList<>();
  4. List<Integer> path=new ArrayList<>();
  5. Arrays.sort(nums);
  6. dfs(nums,0,path,res);
  7. return res;
  8. }
  9. private void dfs(int[] nums, int index,List<Integer> path, List<List<Integer>> res) {
  10. if(path.size()<=nums.length){
  11. res.add(new ArrayList<>(path));
  12. }
  13. List<Integer> pre=new ArrayList<>();
  14. for(int i=index;i<nums.length;i++){
  15. if((path.size()>0&&path.get(path.size()-1)>nums[i])||pre.contains(nums[i])) continue;
  16. pre.add(nums[i]);
  17. path.add(nums[i]);
  18. dfs(nums, i+1, path, res);
  19. path.remove(path.size()-1);
  20. }
  21. }
  22. }

leetcode 17 号算法题:电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = “23”
输出:[“ad”,”ae”,”af”,”bd”,”be”,”bf”,”cd”,”ce”,”cf”]

  1. class Solution {
  2. public List<String> letterCombinations(String digits) {
  3. List<String> res=new ArrayList<>();
  4. if(digits==null||digits.length()==0)
  5. return res;
  6. HashMap<Character,String> map=new HashMap<>();
  7. map.put('2', "abc");
  8. map.put('3', "def");
  9. map.put('4', "ghi");
  10. map.put('5', "jkl");
  11. map.put('6', "mno");
  12. map.put('7', "pqrs");
  13. map.put('8', "tuv");
  14. map.put('9', "wxyz");
  15. dfs(digits,0,map,new StringBuilder(),res);
  16. return res;
  17. }
  18. private void dfs(String digits,int index,HashMap<Character,String> map,StringBuilder builder,List<String> res) {
  19. if(builder.length()==digits.length()){
  20. res.add(builder.toString());
  21. }else {
  22. char ch = digits.charAt(index);
  23. String str = map.get(ch);
  24. int len=str.length();
  25. for(int i=0; i<len;i++){
  26. builder.append(str.charAt(i));
  27. dfs(digits, index+1, map, builder, res);
  28. builder.deleteCharAt(builder.length()-1);
  29. }
  30. }
  31. }
  32. }
  33. class Solution {
  34. public List<String> letterCombinations(String digits) {
  35. List<String> res=new ArrayList<>();
  36. if(digits==null||digits.length()==0)
  37. return res;
  38. HashMap<Character,String> map=new HashMap<>();
  39. map.put('2', "abc");
  40. map.put('3', "def");
  41. map.put('4', "ghi");
  42. map.put('5', "jkl");
  43. map.put('6', "mno");
  44. map.put('7', "pqrs");
  45. map.put('8', "tuv");
  46. map.put('9', "wxyz");
  47. dfs(digits,0,map,"",res);
  48. return res;
  49. }
  50. private void dfs(String digits, int index, HashMap<Character,String> map, String str, List<String> res) {
  51. if(str.length()==digits.length()){
  52. res.add(str);
  53. }else {
  54. char ch = digits.charAt(index);
  55. String temp = map.get(ch);
  56. for(char strToCh:temp.toCharArray()){
  57. dfs(digits, index+1, map, str+strToCh, res);
  58. }
  59. }
  60. }
  61. }

leetcode 93 号算法题:复原 IP 地址

给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:”0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、”192.168.1.312” 和 “192.168@1.1” 是 无效 IP 地址。
示例 1:
输入:s = “25525511135”
输出:[“255.255.11.135”,”255.255.111.35”]

  1. class Solution {
  2. public List<String> restoreIpAddresses(String s) {
  3. List<String> res = new ArrayList<>();
  4. int len=s.length();//要分割的字符串长度
  5. if (len<4||len>12) { //如果长度不够或者过大,则不搜索
  6. return res;
  7. }
  8. Deque<String> path=new ArrayDeque<>(4);//存放每一个符合规则的ip地址
  9. int splitTimes=0;//已经分割成ip地址的段数
  10. dfs(s,len,splitTimes,0,path,res);
  11. return res;
  12. }
  13. //判断 s 的子区间 [left, right] 是否能够成为一个 ip 段。判断的同时顺便把类型转了
  14. private int judgeIfIpSegment(String s,int left,int right){
  15. int len=right-left + 1;
  16. if(len>1&&s.charAt(left)=='0') {//大于1位的时候,不能以0开头
  17. return -1;
  18. }
  19. int res=0;
  20. for (int i=left;i<=right;i++) { //转成int类型
  21. res=res*10+s.charAt(i)-'0';
  22. }
  23. if (res>255) {//大于255不是合法的 ip 段数值
  24. return -1;
  25. }
  26. return res;
  27. }
  28. private void dfs(String s,int len,int split,int begin,Deque<String> path,List<String> res) {
  29. if (begin==len) {//到达字符串末尾
  30. if (split==4) {//并且分隔成为了4段的ip
  31. res.add(String.join(".", path));
  32. }
  33. return;
  34. }
  35. // 看到剩下位数的不够或者超出了,就退出(剪枝),len-begin表示剩余的还未分割的字符串的位数
  36. if (len-begin<(4-split)||len-begin >3*(4-split)) {
  37. return;
  38. }
  39. for (int i=0;i<3;i++) {//每个ip地址段都可以取1-3位的数字
  40. if (begin+i>=len) {//剩下的数字不够取了,则结束
  41. break;
  42. }
  43. int ipSegment=judgeIfIpSegment(s,begin,begin + i);
  44. if (ipSegment!=-1) { // 在判断是 ip 段的情况下,才去做截取
  45. path.addLast(ipSegment + "");
  46. dfs(s,len,split + 1,begin+i+1,path,res);
  47. path.removeLast();
  48. }
  49. }
  50. }
  51. }
  52. public class Solution {
  53. static final int SEG_COUNT=4;
  54. List<String> res=new ArrayList<>();
  55. int[] segments=new int[SEG_COUNT];
  56. public List<String> restoreIpAddresses(String s) {
  57. dfs(s,0,0);
  58. return res;
  59. }
  60. public void dfs(String s,int segId,int segStart) {
  61. if (segId==SEG_COUNT) {// 如果找到了 4 段 IP 地址
  62. if (segStart==s.length()) {//并且遍历完了字符串,那么就是一种答案
  63. StringBuffer ipAddr=new StringBuffer();
  64. for (int i=0;i<SEG_COUNT;i++) {
  65. ipAddr.append(segments[i]);
  66. if (i!=SEG_COUNT-1) {//不是最后一段的话,就加上ip地址的分割符号 .
  67. ipAddr.append('.');
  68. }
  69. }
  70. res.add(ipAddr.toString());
  71. }
  72. return;
  73. }
  74. if (segStart==s.length()) { //能走到这,说明还没有找到4段IP地址,但已经遍历完了字符串,那么提前回溯
  75. return;
  76. }
  77. if (s.charAt(segStart)=='0') {//由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0
  78. segments[segId] = 0;
  79. dfs(s,segId+1,segStart+1);
  80. }
  81. int addr=0; //一般情况,枚举每一种可能性并递归
  82. for (int segEnd=segStart;segEnd<s.length();segEnd++) {
  83. addr =addr*10+(s.charAt(segEnd)-'0');
  84. if(addr>0&&addr<=0xFF){
  85. segments[segId]=addr;
  86. dfs(s,segId+1,segEnd + 1);
  87. }else{
  88. break;
  89. }
  90. }
  91. }
  92. }

leetcode 22 号算法题:括号生成

https://leetcode-cn.com/problems/generate-parentheses/solution/pei-yang-chou-xiang-si-wei-hui-su-jie-fa-7dwu/
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:[“((()))”,”(()())”,”(())()”,”()(())”,”()()()”]
示例 2:
输入:n = 1
输出:[“()”]
提示:
1 <= n <= 8

  1. class Solution {
  2. public List<String> generateParenthesis(int n) {
  3. List<String> res=new ArrayList<>();
  4. if(n<1) return res;
  5. dfs(n,"",res,0,0);
  6. return res;
  7. }
  8. private void dfs(int n,String path,List<String> res,int open,int close){
  9. if(close>open||open>n)
  10. return;
  11. if(2*n==path.length()){
  12. res.add(path);
  13. return;
  14. }
  15. dfs(n, path+"(", res,open+1,close);
  16. dfs(n, path+")", res,open,close+1);
  17. }
  18. }
  19. class Solution {
  20. public List<String> generateParenthesis(int n) {
  21. List<String> res=new ArrayList<>();
  22. if(n<1) return res;
  23. dfs(n,"",res,0,0);
  24. return res;
  25. }
  26. private void dfs(int n,String path,List<String> res,int open,int close){
  27. if(2*n==path.length()){
  28. res.add(path);
  29. return;
  30. }
  31. if(open<n)
  32. dfs(n, path+"(", res,open+1,close);
  33. if(close<open)
  34. dfs(n, path+")", res,open,close+1);
  35. }
  36. }

leetcode 51 号算法题:N 皇后

  1. //找到所有的n皇后摆放位置
  2. class Solution {
  3. private void check(int[] array,int max,int n,List<int[]> res){//放置第n个皇后
  4. if(n==max){
  5. res.add(Arrays.copyOf(array, array.length));
  6. return;
  7. }
  8. for(int i=0;i<max;i++){//放置第n个皇后时:从这行0-max处一次放置,看是否会冲突
  9. array[n]=i;
  10. if(judge(array,n)){//当前位置不冲突,则放置下一个皇后
  11. check(array,max,n+1,res);
  12. }
  13. }
  14. }
  15. private boolean judge(int[] array,int n){//判断当前皇后放在此处(这行的第n个位置)是否冲突
  16. for(int i=0; i<n;i++){//与前n-1个皇后的位置进行比较,看是否冲突
  17. //array[i] == array[n] 看是否在同一列上
  18. //Math.abs(n - i) == Math.abs(array[n] - array[i]) 看是否在同一斜线上
  19. if (array[i]==array[n]||Math.abs(n-i)==Math.abs(array[n]-array[i])){
  20. return false;
  21. }
  22. }
  23. return true;
  24. }
  25. }
  1. class Solution {
  2. public List<List<String>> solveNQueens(int n) {
  3. int[] array=new int[n];
  4. List<List<String>> res=new ArrayList<>();
  5. check(array, n, 0, res);
  6. return res;
  7. }
  8. private void check(int[] array,int max,int n,List<List<String>> res){//放置第n个皇后
  9. if(n==max){
  10. List<String> path=new ArrayList<>();
  11. for(int i=0;i<max;i++){
  12. char[] chars=new char[max];
  13. Arrays.fill(chars, '.');
  14. chars[array[i]]='Q';
  15. path.add(new String(chars));
  16. }
  17. res.add(new ArrayList<>(path));
  18. return;
  19. }
  20. for(int i=0;i<max;i++){//放置第n个皇后时:从这行0-max处一次放置,看是否会冲突
  21. array[n]=i;
  22. if(judge(array,n)){//当前位置不冲突,则放置下一个皇后
  23. check(array,max,n+1,res);
  24. }
  25. }
  26. }
  27. private boolean judge(int[] array,int n){//判断当前皇后放在此处(这行的第n个位置)是否冲突
  28. for(int i=0; i<n;i++){//与前n-1个皇后的位置进行比较,看是否冲突
  29. //array[i] == array[n] 看是否在同一列上
  30. //Math.abs(n - i) == Math.abs(array[n] - array[i]) 看是否在同一斜线上
  31. if (array[i]==array[n]||Math.abs(n-i)==Math.abs(array[n]-array[i])){
  32. return false;
  33. }
  34. }
  35. return true;
  36. }
  37. }
  38. class Solution {
  39. public List<List<String>> solveNQueens(int n) {
  40. List<List<String>> res=new ArrayList<>();
  41. char[][] board=new char[n][n];
  42. for (int i=0;i<board.length;i++) {
  43. Arrays.fill(board[i],'.');
  44. }
  45. check(board, 0, res);
  46. return res;
  47. }
  48. private void check( char[][] board,int index,List<List<String>> res){
  49. if(index==board.length){
  50. List<String> temp=new ArrayList<>();
  51. for (int i=0;i<board.length;i++) {
  52. temp.add(new String(Arrays.copyOf(board[i],board.length)));
  53. }
  54. res.add(new ArrayList<>(temp));
  55. return;
  56. }
  57. for(int i=0;i<board.length;i++){//列
  58. if(isVisited(board,index,i,'Q'))
  59. continue;
  60. board[index][i]='Q';
  61. check(board, index+1, res);
  62. board[index][i]='.';
  63. }
  64. }
  65. private boolean isVisited(char[][] board, int row, int col, char target) {
  66. for (int i=0; i<row; i++) {
  67. if(board[i][col]==target)
  68. return true;
  69. }
  70. if(row>0&&col>0) {
  71. for (int i=row-1,j=col-1;i>=0&&j>=0;i--,j--) { //左上角
  72. if (board[i][j] == target)
  73. return true;
  74. }
  75. }
  76. if(row>0){
  77. for(int i=row-1,j=col+1;i>=0&&j<board.length;i--,j++){ //右上角
  78. if(board[i][j]==target)
  79. return true;
  80. }
  81. }
  82. return false;
  83. }
  84. }

leetcode 37 号算法题:数独问题

  1. class Solution {
  2. public void solveSudoku(char[][] board) {
  3. backtrack(board, 0, 0);
  4. }
  5. private boolean backtrack(char[][] board,int i,int j){//i是行,j是列
  6. if(j==board[0].length){
  7. return backtrack(board, i+1, 0);
  8. }
  9. if(i==board.length){
  10. return true;
  11. }
  12. if(board[i][j]!='.'){
  13. return backtrack(board, i, j+1);
  14. }
  15. for(char ch='1';ch<='9';ch++){
  16. if(isVisited(board,i,j,ch)) {
  17. continue;
  18. }
  19. board[i][j]=ch;
  20. if(backtrack(board, i, j+1))
  21. return true;
  22. board[i][j]='.';
  23. }
  24. return false;
  25. }
  26. private boolean isVisited(char[][] board,int m,int n,char target) {
  27. for(int i=0;i<board.length;i++){
  28. if(board[m][i]==target)
  29. return true;
  30. if(board[i][n]==target)
  31. return true;
  32. if(board[(m/3)*3+i/3][(n/3)*3+i%3]==target)
  33. return true;
  34. }
  35. return false;
  36. }
  37. }

贪心算法思想

leetcode 455 号算法题:分发饼干

leetcode 322 号算法题:零钱兑换

leetcode 45 号算法题:跳跃游戏 Ⅱ

leetcode 1578 号算法题:避免重复字母的最小删除成本

leetcode 402 号算法题:移掉K位数字

动态规划算法思想

leetcode 509 号算法题:斐波那契数

leetcode 322 号算法题:零钱兑换

leetcode 64 号算法题:最小路径和

leetcode 53 号算法题:最大子数组之和

leetcode 647 号算法题:回文子串

leetcode 5 号算法题:最长回文串

leetcode 131 号算法题:分割回文串

leetcode 516 号算法题:最长回文子序列

leetcode 300 号算法题:最长上升子序列

leetcode 1143 号算法题:最长公共子序列

leetcode 72 号算法题:编辑距离

leetcode 44 号算法题:通配符匹配

leetcode 10 号算法题:正则表达式匹配

leetcode 486 号算法题:预测赢家

leetcode 877 号算法题:石子游戏

0-1 背包问题

总结

1.深度优先搜索遍历二叉树的所有路径

  1. //深度优先遍历
  2. public List<List<Integer>> pathSum(TreeNode root) {
  3. List<List<Integer>> res=new ArrayList<>();
  4. LinkedList<Integer> path=new LinkedList<>();
  5. dfs(root,path,res);
  6. return res;
  7. }
  8. private void dfs(TreeNode node,LinkedList<Integer> path,List<List<Integer>> res){
  9. if(node==null) return;
  10. path.addLast(node.val);
  11. if(node.left==null&&node.right==null){
  12. res.add(new ArrayList<>(path));
  13. }
  14. dfs(node.left, path, res);
  15. dfs(node.right, path, res);
  16. path.removeLast();
  17. }
  18. List<List<Integer>> res=new ArrayList<>();
  19. LinkedList<Integer> path=new LinkedList<>();
  20. public List<List<Integer>> pathSum(TreeNode root) {
  21. dfs(root);
  22. return res;
  23. }
  24. private void dfs(TreeNode node){
  25. if(node==null) return;
  26. path.addLast(node.val);
  27. if(node.left==null&&node.right==null){
  28. res.add(new ArrayList<>(path));
  29. }
  30. dfs(node.left);
  31. dfs(node.right);
  32. path.removeLast();
  33. }
  34. //广度优先遍历
  35. public List<List<Integer>> pathSum(TreeNode root) {
  36. List<List<Integer>> res=new ArrayList<>();
  37. Stack<TreeNode> stack=new Stack<>();
  38. List<Integer> path=new ArrayList<>();
  39. TreeNode curr=root;
  40. TreeNode lastVisited=null;
  41. while (curr!=null||!stack.isEmpty()){
  42. while (curr!=null){
  43. stack.add(curr);
  44. path.add(curr.val);
  45. curr=curr.left;
  46. }
  47. curr=stack.peek();
  48. if(curr.right!=null&&curr.right!=lastVisited){
  49. curr=curr.right;
  50. continue;
  51. }
  52. if(curr.right==null&&curr.left==null){
  53. res.add(new ArrayList<>(path));
  54. }
  55. path.remove(path.size()-1);
  56. curr=stack.pop();
  57. lastVisited=curr;
  58. curr=null;
  59. }
  60. return res;
  61. }

四、刷题总结

1.关于递归反转链表

  1. private ListNode traverse(ListNode head) {//反转整个链表
  2. if(head.next==null)
  3. return head;
  4. ListNode last=traverse(head.next);
  5. head.next.next=head;
  6. head.next=null;
  7. return last;
  8. }
  9. ListNode after;
  10. private ListNode traverse(ListNode head,int m) {//反转链表的前n个节点
  11. if(m==1){
  12. after=head.next;
  13. return head;
  14. }
  15. ListNode last=traverse(head.next,m-1);
  16. head.next.next=head;
  17. head.next=after;
  18. return last;
  19. }
  20. private ListNode traverse(ListNode head,int m,int n) {//反转链表 m-n 之间的节点
  21. if(m==1){
  22. return traverse(head,n);
  23. }
  24. head.next=traverse(head.next,m-1,n-1);
  25. return head;
  26. }