LeetCode 300最长递增子序列(贪心 + 二分查找比nums[i]小的第一个元素下标)、LeetCode 200岛屿数量(深搜)、LeetCode 494目标和(dfs回溯)

Top 1:LeetCode 300最长递增子序列(贪心 + 二分查找)

题目描述:
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1

二分法查找满足条件元素下标链接:二分法

一、设当前已求出的最长上升子序列的长度为len(初始时len=1,)构造一个递增的列表d(d[1]=nums[0]

二、遇到nums[i]的时候,如果nums[i]>d[len]就更新d[++len] = nums[i];否则,在d数组中二分查找到第一个比nums[i]小的元素下标pos,并更新d[pos + 1] = nums[i](替换掉对应元素)

在递增列表中二分查找第一个比nums[i]小的元素下标代码如下:

int l = 1, r = len, pos = 0;
while (l <= r) {
    int mid = (l + r) >> 1;
    if (d[mid] < nums[i]) {
        pos = mid;
        l = mid + 1;
    } else {
        r = mid - 1;
    }
}
d[pos + 1] = nums[i];   // 没找到说明d[]里面的所有数都比nums[i]大,此时更新d[1]【nums[i]对应的是d[i+1]】

三、初始pos=0是为了防止while循环二分找不到(即所有元素都比nums[i]大),的时候更新d[0+1]=nums[i]

思路理解:

0 8 4 12 2 3 4 更新为 0 3 4 但是不影响结果【刚开始只维护0 4 12 更新12之前的(遇到比12大的也更新12)
所以企业级理解如下:
在这里插入图片描述

二分法寻找第一个比nums[i]小的元素下标理解:

在这里插入图片描述

当存在重复元素的时候1 1 1 3 插入 2 的时候此方法也是可以找到 3 前面的那个 1,之后pos+1替换 32 

当l=1,r=len=8下标的时候,下表和除以二得到的是中间的上半部分末尾的元素下标
例如我们插入2进去,按照【d[mid]<nums[i] 就pos=mid,+1,else 就-1的逻辑】,此时应该r=mid-1,之后当2=2的时候也要-1,就找到了pos,接着l+1跳出while(l<=r)循环,就找到了1元素的下标1
在这里插入图片描述

  • 时间复杂度:数组 nums 的长度为 n,我们依次用数组中的元素去更新 d 数组,而更新 d 数组时需要进行 O(logn) 的二分搜索,所以总时间复杂度为 O(nlogn)。
  • 空间复杂度:需要额外使用长度为 n+1 的 d 数组。

可通过完整代码:

public int lengthOfLIS(int[] nums) {
    int len = 1, n = nums.length;
    if (n == 0) return 0;
    int[] d = new int[n + 1];
    d[len] = nums[0];
    for (int i = 1; i < n; i++) {
        if (nums[i] > d[len]) {
            d[++len] = nums[i];   // 此处是++len
        } else {   // 此二分法如果没有上面的if,当i大于所有d[]的时候,将会返回d数组最后一个下标。因为有了上面的条件,所以d[pos+1不会越界]
            int l = 1, r = len, pos = 0;
            while (l <= r) {   // 二分法,在第一个递增列表中查找第一个比nums[i]小的元素   // l<=r有等号是方便只剩下一个元素的时候继续【比如 1 1 1 3 插入 2 只剩下3的时候再r=mid+1跳出while循环】
                int mid = (l + r) >> 1;
                if (d[mid] < nums[i]) {   // 中间的比i小,i应该在右边
                    pos = mid;
                    l = mid + 1;
                } else {
                     r = mid - 1;   // 等于的时候也r=mid-1,不影响最后结果【1 1,或者1】
                }
            }
            d[pos + 1] = nums[i];   // pos+1不会越界,因为else下,d[]中永远有比nums[i]大的存在
        }
    }
    return len;
}

在这里插入图片描述

Top2:LeetCode 200岛屿数量(深搜)

题目描述:
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:
输入:grid = [
[“1”,“1”,“1”,“1”,“0”],
[“1”,“1”,“0”,“1”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“0”,“0”,“0”]
]
输出:1

示例 2:
输入:grid = [
[“1”,“1”,“0”,“0”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“1”,“0”,“0”],
[“0”,“0”,“0”,“1”,“1”]
]
输出:3

一、双层for循环遍历整个网格,如果grid[r][c] == '1'就num_isLand++,然后dfs上下左右深搜置’0’,截止条件有到边界grid[r][c]=='0'

  • 时间复杂度:O(MN),其中M和N分别为行数和列数。【这个不是很理解,参考全排列:递归的时间复杂度
  • 空间复杂度:O(MN),最坏情况下,整个网格均为陆地,深度优先搜索的深度达到MN

可通过完整代码

public int numIslands(char[][] grid) {
    if (grid == null || grid.length == 0) return 0;
    int nr = grid.length;
    int nc = grid[0].length;
    int numIslands = 0;
    for (int r = 0; r < nr; r++) {
        for (int c = 0; c < nc; c++) {
            if (grid[r][c] == '1') {
                numIslands++;
                dfs(grid, r, c);
            }
        }
    }
    return numIslands;
}

private void dfs(char[][] grid, int r, int c) {
    int nr = grid.length;
    int nc = grid[0].length;
    if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] == '0') {
        return;
    }
    grid[r][c] = '0';
    dfs(grid, r - 1, c);
    dfs(grid, r + 1, c);
    dfs(grid, r, c - 1);
    dfs(grid, r, c + 1);
}

在这里插入图片描述

Top3:LeetCode 494目标和(dfs回溯)

题目描述:
给你一个整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :

例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3

示例 2:
输入:nums = [1], target = 1
输出:1

一、数组 nums 的每个元素都可以添加符号 + 或 -,因此每个元素有 2 种添加符号的方法,n 个数共有 2^n 种添加符号的方法

回溯过程dfs(nums,target,index,sum)中维护一个计数器 count,当遇到一种表达式的结果等于目标数 target 时,将 count 的值加 11。遍历完所有的表达式之后,即可得到结果等于目标数target 的表达式的数目。

  • 时间复杂度:回溯需要遍历所有不同的表达式,共有 2^n 种不同的表达式,其中n是nums.length,因此总时间复杂度为O(2的n次方)
  • 空间复杂度:空间复杂度主要取决于递归调用的栈空间,栈的深度不超过 n。所以为O(n)

可通过完整代码:

int count = 0;
public int findTargetSumWays(int[] nums, int target) {
    backtrack(nums, target, 0, 0);
    return count;
}

private void backtrack(int[] nums, int target, int index, int sum) {
    if (index == nums.length) {
        if (sum == target) count++;
    } else {
        backtrack(nums, target, index + 1, sum + nums[index]);
        backtrack(nums, target, index + 1, sum - nums[index]);
    }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值