爬楼梯(simple难度)
https://leetcode-cn.com/problems/climbing-stairs/
与本题相似题目:
class Solution {
public int climbStairs(int n) {
int p = 0, q = 0, r = 1;
for (int i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/climbing-stairs/solution/pa-lou-ti-by-leetcode-solution/
来源:力扣(LeetCode)
比特位计数(medium难度)
https://leetcode-cn.com/problems/counting-bits/
<方法一>:布赖恩▪克尼根算法(本博文前文的关于《汉明距离》的题解中有关于布赖恩▪克尼根算法的介绍)
public class Solution {
public int[] countBits(int num) {
int[] ans = new int[num + 1];
for (int i = 0; i <= num; ++i)
ans[i] = popcount(i);
return ans;
}
private int popcount(int x) {
int count;
for (count = 0; x != 0; ++count)
x &= x - 1; //将最低有效非零位归零
return count;
}
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/counting-bits/solution/bi-te-wei-ji-shu-by-leetcode/
来源:力扣(LeetCode)
<方法二>:动态规划 + 最高有效位
public class Solution {
public int[] countBits(int num) {
int[] ans = new int[num + 1];
int i = 0, b = 1;
// [0, b) 已经计算出来
while (b <= num) {
// 从[0, b)产生[b, 2b) 或 [b, num)
while(i < b && i + b <= num){
ans[i + b] = ans[i] + 1;
++i;
}
i = 0; // i置0
b <<= 1; // b = 2b
}
return ans;
}
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/counting-bits/solution/bi-te-wei-ji-shu-by-leetcode/
来源:力扣(LeetCode)
<方法三>:动态规划 + 最低有效位
public class Solution {
public int[] countBits(int num) {
int[] ans = new int[num + 1];
for (int i = 1; i <= num; ++i)
ans[i] = ans[i >> 1] + (i & 1); //x >> 1表示 x / 2 , x & 1 表示 x % 2
return ans;
}
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/counting-bits/solution/bi-te-wei-ji-shu-by-leetcode/
来源:力扣(LeetCode)
本方法另一种理解方式:
作者:duadua
链接:https://leetcode-cn.com/problems/counting-bits/solution/hen-qing-xi-de-si-lu-by-duadua/
来源:力扣(LeetCode)
<方法四>:动态规划 + 最后设置位
public class Solution {
public int[] countBits(int num) {
int[] ans = new int[num + 1];
for (int i = 1; i <= num; ++i)
ans[i] = ans[i & (i - 1)] + 1;
return ans;
}
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/counting-bits/solution/bi-te-wei-ji-shu-by-leetcode/
来源:力扣(LeetCode)
复杂度同方法三。
最长不含重复字符的子字符串(medium难度)
https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/
与本题相同题目:
本方法思路和代码来源:
作者:guanpengchn
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/hua-jie-suan-fa-3-wu-zhong-fu-zi-fu-de-zui-chang-z/
来源:力扣(LeetCode)
class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length(), ans = 0;
Map<Character, Integer> map = new HashMap<>();
for (int end = 0, start = 0; end < n; end++) {
char alpha = s.charAt(end);
if (map.containsKey(alpha)) {
start = Math.max(map.get(alpha), start);
}
ans = Math.max(ans, end - start + 1);
map.put(s.charAt(end), end + 1);
}
return ans;
}
}
作者:guanpengchn
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/hua-jie-suan-fa-3-wu-zhong-fu-zi-fu-de-zui-chang-z/
来源:力扣(LeetCode)
以下方法思路及代码的来源:
作者:jyd
链接:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/solution/mian-shi-ti-10-i-fei-bo-na-qi-shu-lie-dong-tai-gui/
来源:力扣(LeetCode)
Java的 getOrDefault(key, default)getOrDefault(key,default),代表当哈希表包含键key时返回对应value,不包含时返回默认值default。
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();
int res = 0, tmp = 0;
for(int j = 0; j < s.length(); j++) {
int i = dic.getOrDefault(s.charAt(j), -1); // 获取索引 i
dic.put(s.charAt(j), j); // 更新哈希表
tmp = tmp < j - i ? tmp + 1 : j - i; // dp[j - 1] -> dp[j]
res = Math.max(res, tmp); // max(dp[j - 1], dp[j])
}
return res;
}
}
作者:jyd
链接:https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/solution/mian-shi-ti-48-zui-chang-bu-han-zhong-fu-zi-fu-d-9/
来源:力扣(LeetCode)
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();
int res = 0, tmp = 0;
for(int j = 0; j < s.length(); j++) {
int i = j - 1;
while(i >= 0 && s.charAt(i) != s.charAt(j)) i--; // 线性查找 i
tmp = tmp < j - i ? tmp + 1 : j - i; // dp[j - 1] -> dp[j]
res = Math.max(res, tmp); // max(dp[j - 1], dp[j])
}
return res;
}
}
作者:jyd
链接:https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/solution/mian-shi-ti-48-zui-chang-bu-han-zhong-fu-zi-fu-d-9/
来源:力扣(LeetCode)
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();
int i = -1, res = 0;
for(int j = 0; j < s.length(); j++) {
if(dic.containsKey(s.charAt(j)))
i = Math.max(i, dic.get(s.charAt(j))); // 更新左指针 i
dic.put(s.charAt(j), j); // 哈希表记录
res = Math.max(res, j - i); // 更新结果
}
return res;
}
}
作者:jyd
链接:https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/solution/mian-shi-ti-48-zui-chang-bu-han-zhong-fu-zi-fu-d-9/
来源:力扣(LeetCode)
买卖股票的最佳时机(simple难度)
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
与本题相同的题目:
<方法一>:动态规划
本方法思路及代码来源:
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/bao-li-mei-ju-dong-tai-gui-hua-chai-fen-si-xiang-b/
来源:力扣(LeetCode)
参考代码2:
public class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
// 特殊判断
if (len < 2) {
return 0;
}
int[][] dp = new int[len][2];
// dp[i][0] 下标为 i 这天结束的时候,不持股,手上拥有的现金数
// dp[i][1] 下标为 i 这天结束的时候,持股,手上拥有的现金数
// 初始化:不持股显然为 0,持股就需要减去第 1 天(下标为 0)的股价
dp[0][0] = 0;
dp[0][1] = -prices[0];
// 从第 2 天开始遍历
for (int i = 1; i < len; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
}
return dp[len - 1][0];
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/bao-li-mei-ju-dong-tai-gui-hua-chai-fen-si-xiang-b/
来源:力扣(LeetCode)
参考代码3:
public class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if (len < 2) {
return 0;
}
int[][] dp = new int[2][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < len; i++) {
dp[i % 2][0] = Math.max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] + prices[i]);
dp[i % 2][1] = Math.max(dp[(i - 1) % 2][1], -prices[i]);
}
return dp[(len - 1) & 1][0];
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/bao-li-mei-ju-dong-tai-gui-hua-chai-fen-si-xiang-b/
来源:力扣(LeetCode)
参考代码4:
public class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if (len < 2) {
return 0;
}
int[] dp = new int[2];
dp[0] = 0;
dp[1] = -prices[0];
for (int i = 1; i < len; i++) {
dp[0] = Math.max(dp[0], dp[1] + prices[i]);
dp[1] = Math.max(dp[1], -prices[i]);
}
return dp[0];
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/bao-li-mei-ju-dong-tai-gui-hua-chai-fen-si-xiang-b/
来源:力扣(LeetCode)
最大子序和(simple难度)
https://leetcode-cn.com/problems/maximum-subarray/
与本题相同的题目:
本方法思路和代码来源:
作者:guanpengchn
链接:https://leetcode-cn.com/problems/maximum-subarray/solution/hua-jie-suan-fa-53-zui-da-zi-xu-he-by-guanpengchn/
来源:力扣(LeetCode)
class Solution {
public int maxSubArray(int[] nums) {
int ans = nums[0];
int sum = 0;
for(int num: nums) {
if(sum > 0) {
sum += num;
} else {
sum = num;
}
ans = Math.max(ans, sum);
}
return ans;
}
}
作者:guanpengchn
链接:https://leetcode-cn.com/problems/maximum-subarray/solution/hua-jie-suan-fa-53-zui-da-zi-xu-he-by-guanpengchn/
来源:力扣(LeetCode)
本题思路及代码的来源:
作者:jyd
链接:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/solution/mian-shi-ti-10-i-fei-bo-na-qi-shu-lie-dong-tai-gui/
来源:力扣(LeetCode)
class Solution {
public int maxSubArray(int[] nums) {
int res = nums[0];
for(int i = 1; i < nums.length; i++) {
nums[i] += Math.max(nums[i - 1], 0);
res = Math.max(res, nums[i]);
}
return res;
}
}
零钱兑换(medium难度)
https://leetcode-cn.com/problems/coin-change/
<方法一>:动态规划:自顶而下
首先定义:
public class Solution {
public int coinChange(int[] coins, int amount) {
if (amount < 1) {
return 0;
}
return coinChange(coins, amount, new int[amount]);
}
private int coinChange(int[] coins, int rem, int[] count) {
if (rem < 0) {
return -1;
}
if (rem == 0) {
return 0;
}
if (count[rem - 1] != 0) {
return count[rem - 1];
}
int min = Integer.MAX_VALUE;
for (int coin : coins) {
int res = coinChange(coins, rem - coin, count);
if (res >= 0 && res < min) {
min = 1 + res;
}
}
count[rem - 1] = (min == Integer.MAX_VALUE) ? -1 : min;
return count[rem - 1];
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/coin-change/solution/322-ling-qian-dui-huan-by-leetcode-solution/
来源:力扣(LeetCode)
<方法二>:动态规划:自下而上
public class Solution {
public int coinChange(int[] coins, int amount) {
int max = amount + 1;
// dp[n]的值: 表示的凑成总金额为n所需的最少的硬币个数
int[] dp = new int[amount + 1];
// 给dp赋初值,最多的硬币数就是全部使用面值1的硬币进行换
// amount + 1 是不可能达到的换取数量,于是使用其进行填充
Arrays.fill(dp, max);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
// dp[i]有两种实现的方式,
// 一种是包含当前的coins[i],那么剩余钱就是 i-coins[i],这种操作要兑换的硬币数是 dp[i-coins[j]] + 1
// 另一种就是不包含,要兑换的硬币数是dp[i]
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/coin-change/solution/322-ling-qian-dui-huan-by-leetcode-solution/
来源:力扣(LeetCode)
最长递增子序列(medium难度)
https://leetcode-cn.com/problems/longest-increasing-subsequence/
本方法思路和代码来源:
作者:jyd
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-dong-tai-gui-hua-2/
来源:力扣(LeetCode)
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums.length == 0) return 0;
int[] dp = new int[nums.length];
int res = 0;
Arrays.fill(dp, 1);
for(int i = 0; i < nums.length; i++) {
for(int j = 0; j < i; j++) {
if(nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
}
res = Math.max(res, dp[i]);
}
return res;
}
}
作者:jyd
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-dong-tai-gui-hua-2/
来源:力扣(LeetCode)
// Dynamic programming + Dichotomy.
class Solution {
public int lengthOfLIS(int[] nums) {
int[] tails = new int[nums.length];
int res = 0;
for(int num : nums) {
int i = 0, j = res;
while(i < j) {
int m = (i + j) / 2;
if(tails[m] < num) i = m + 1;
else j = m;
}
tails[i] = num;
//res++从而新开一个空间,若找到的数字大于前面的tails[i],则存放到tails[res];
if(res == j) res++;
}
return res;
}
}
作者:jyd
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-dong-tai-gui-hua-2/
来源:力扣(LeetCode)
本方法思路和代码来源:
公众号:labuladong
作者:labuladong
公众号labuladong关于此题的详解(用蜘蛛纸牌解析动态规划+二分查找相当精妙)
最长回文子串(medium难度)
https://leetcode-cn.com/problems/longest-palindromic-substring/
本题方法思路和代码来源:
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/
来源:力扣(LeetCode)
参考代码1:
public class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
int begin = 0;
// s.charAt(i) 每次都会检查数组下标越界,因此先转换成字符数组
char[] charArray = s.toCharArray();
// 枚举所有长度大于 1 的子串 charArray[i..j]
for (int i = 0; i < len - 1; i++) {
for (int j = i + 1; j < len; j++) {
if (j - i + 1 > maxLen && validPalindromic(charArray, i, j)) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
/**
* 验证子串 s[left..right] 是否为回文串
*/
private boolean validPalindromic(char[] charArray, int left, int right) {
while (left < right) {
if (charArray[left] != charArray[right]) {
return false;
}
left++;
right--;
}
return true;
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/
来源:力扣(LeetCode)
参考代码2:
public class Solution {
public String longestPalindrome(String s) {
// 特判
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
int begin = 0;
// dp[i][j] 表示 s[i, j] 是否是回文串
boolean[][] dp = new boolean[len][len];
char[] charArray = s.toCharArray();
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
for (int j = 1; j < len; j++) {
for (int i = 0; i < j; i++) {
if (charArray[i] != charArray[j]) {
dp[i][j] = false;
} else {
if (j - i < 3) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
}
// 只要 dp[i][j] == true 成立,就表示子串 s[i..j] 是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/
来源:力扣(LeetCode)
参考代码3:
import java.util.Arrays;
public class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
boolean[][] dp = new boolean[len][len];
for (int i = 0; i < len; i++) {
Arrays.fill(dp[i], false);
}
// 初始化
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
char[] charArray = s.toCharArray();
int maxLen = 1;
int start = 0;
for (int j = 1; j < len; j++) {
// 只有下面这一行和「参考代码 2」不同,i 正着写、倒过来写都行,因为子串都有参考值
for (int i = j - 1; i >= 0; i--) {
if (charArray[i] == charArray[j]) {
if (j - i < 3) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
} else {
dp[i][j] = false;
}
// 只要 dp[i][j] == true 成立,就表示子串 s[i, j] 是回文,此时记录回文长度和起始位置
if (dp[i][j]) {
int curLen = j - i + 1;
if (curLen > maxLen) {
maxLen = curLen;
start = i;
}
}
}
}
return s.substring(start, start + maxLen);
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/
来源:力扣(LeetCode)
参考代码4:
import java.util.Arrays;
public class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
boolean[][] dp = new boolean[len][len];
for (int i = 0; i < len; i++) {
Arrays.fill(dp[i], false);
}
// 初始化
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
char[] charArray = s.toCharArray();
int maxLen = 1;
int start = 0;
for (int j = 1; j < len; j++) {
for (int i = 0; i < j; i++) {
dp[i][j] = charArray[i] == charArray[j] && (j - i < 3 || dp[i + 1][j - 1]);
// 只要 dp[i][j] == true 成立,就表示子串 s[i, j] 是回文,此时记录回文长度和起始位置
if (dp[i][j]) {
int curLen = j - i + 1;
if (curLen > maxLen) {
maxLen = curLen;
start = i;
}
}
}
}
return s.substring(start, start + maxLen);
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/
来源:力扣(LeetCode)
参考代码5:
public class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
String res = s.substring(0, 1);
// 中心位置枚举到 len - 2 即可
for (int i = 0; i < len - 1; i++) {
String oddStr = centerSpread(s, i, i);
String evenStr = centerSpread(s, i, i + 1);
String maxLenStr = oddStr.length() > evenStr.length() ? oddStr : evenStr;
if (maxLenStr.length() > maxLen) {
maxLen = maxLenStr.length();
res = maxLenStr;
}
}
return res;
}
private String centerSpread(String s, int left, int right) {
// left = right 的时候,此时回文中心是一个字符,回文串的长度是奇数
// right = left + 1 的时候,此时回文中心是一个空隙,回文串的长度是偶数
int len = s.length();
int i = left;
int j = right;
while (i >= 0 && j < len) {
if (s.charAt(i) == s.charAt(j)) {
i--;
j++;
} else {
break;
}
}
// 这里要小心,跳出 while 循环时,恰好满足 s.charAt(i) != s.charAt(j),因此不能取 i,不能取 j
return s.substring(i + 1, j);
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/
来源:力扣(LeetCode)
打家劫舍(medium难度)
https://leetcode-cn.com/problems/house-robber/
本方法思路和代码来源:
作者:jyd
链接:https://leetcode-cn.com/problems/house-robber/solution/da-jia-jie-she-dong-tai-gui-hua-jie-gou-hua-si-lu-/
来源:力扣(LeetCode)
class Solution {
public int rob(int[] nums) {
int pre = 0; //dp[n-1]的值
int cur = 0; //dp[n]的值
int tmp; //临时变量
for(int num : nums) {
tmp = cur; //dp[n]暂存到temp,cur之后用来存储dp[n+1]
cur = Math.max(pre + num, cur);//dp[n + 1] = max{ dp[n - 1] + num, dp[n] };
pre = tmp;
}
return cur;
}
}
作者:jyd
链接:https://leetcode-cn.com/problems/house-robber/solution/da-jia-jie-she-dong-tai-gui-hua-jie-gou-hua-si-lu-/
来源:力扣(LeetCode)
不同路径(medium难度)
https://leetcode-cn.com/problems/unique-paths/
class Solution {
public int uniquePaths(int m, int n) {
int[][] f = new int[m][n];
for (int i = 0; i < m; ++i) {
f[i][0] = 1;
}
for (int j = 0; j < n; ++j) {
f[0][j] = 1;
}
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
return f[m - 1][n - 1];
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/unique-paths/solution/bu-tong-lu-jing-by-leetcode-solution-hzjf/
来源:力扣(LeetCode)
由于dp[i][j] = dp[i-1][j] + dp[i][j-1],因此只需要保留当前行与上一行的数据 (在动态方程中,即pre[j] = dp[i-1][j]),两行,空间复杂度O(2n)
class Solution {
public int uniquePaths(int m, int n) {
int[] pre = new int[n];
int[] cur = new int[n];
Arrays.fill(pre, 1);
Arrays.fill(cur,1);
for (int i = 1; i < m;i++){
for (int j = 1; j < n; j++){
cur[j] = cur[j-1] + pre[j];
}
pre = cur.clone();
}
return pre[n-1];
}
}
作者:powcai
链接:https://leetcode-cn.com/problems/unique-paths/solution/dong-tai-gui-hua-by-powcai-2/
来源:力扣(LeetCode)
cur[j] += cur[j-1], 即cur[j] = cur[j] + cur[j-1] 等价于: cur[j] = pre[j] + cur[j-1],因此空间复杂度为O(n).、
class Solution {
public int uniquePaths(int m, int n) {
int[] cur = new int[n];
Arrays.fill(cur,1);
for (int i = 1; i < m;i++){
for (int j = 1; j < n; j++){
cur[j] += cur[j-1] ;
}
}
return cur[n-1];
}
}
作者:powcai
链接:https://leetcode-cn.com/problems/unique-paths/solution/dong-tai-gui-hua-by-powcai-2/
来源:力扣(LeetCode)
class Solution {
public int uniquePaths(int m, int n) {
long ans = 1;
for (int x = n, y = 1; y < m; ++x, ++y) {
ans = ans * x / y;
}
return (int) ans;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/unique-paths/solution/bu-tong-lu-jing-by-leetcode-solution-hzjf/
来源:力扣(LeetCode)
分割等和子集(medium难度)(0-1背包问题,背包问题的基础)
https://leetcode-cn.com/problems/partition-equal-subset-sum/
本题方法思路和代码来源:
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/
来源:力扣(LeetCode)
参考代码1:
public class Solution {
public boolean canPartition(int[] nums) {
int len = nums.length;
// 题目已经说非空数组,可以不做非空判断
int sum = 0;
for (int num : nums) {
sum += num;
}
// 特判:如果是奇数,就不符合要求
if ((sum & 1) == 1) {
return false;
}
int target = sum / 2;
// 创建二维状态数组,行:物品索引,列:容量(包括 0)
boolean[][] dp = new boolean[len][target + 1];
// 先填表格第 0 行,第 1 个数只能让容积为它自己的背包恰好装满
if (nums[0] <= target) {
dp[0][nums[0]] = true;
}
// 再填表格后面几行
for (int i = 1; i < len; i++) {
for (int j = 0; j <= target; j++) {
// 直接从上一行先把结果抄下来,然后再修正
dp[i][j] = dp[i - 1][j];
//j恰好等于nums[i],即单独nums[j] 这个数恰好等于此时j(背包的容积)
if (nums[i] == j) {
dp[i][j] = true;
continue;
}
//不选择nums[i],如果在[0, i - 1]这个子区间内已经有一部分元素,使得它们的和为j ,那么dp[i][j] = true;
//选择nums[i],如果在[0, i - 1]这个子区间内就得找到一部分元素,使得它们的和为j - nums[i]。
if (nums[i] < j) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
}
}
}
return dp[len - 1][target];
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/
来源:力扣(LeetCode)
参考代码2:
public class Solution {
public boolean canPartition(int[] nums) {
int len = nums.length;
int sum = 0;
for (int num : nums) {
sum += num;
}
if ((sum & 1) == 1) {
return false;
}
int target = sum / 2;
boolean[][] dp = new boolean[len][target + 1];
// 初始化成为 true 虽然不符合状态定义,但是从状态转移来说是完全可以的
dp[0][0] = true;
if (nums[0] <= target) {
dp[0][nums[0]] = true;
}
for (int i = 1; i < len; i++) {
for (int j = 0; j <= target; j++) {
dp[i][j] = dp[i - 1][j];
if (nums[i] <= j) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
}
}
// 由于状态转移方程的特殊性,提前结束,可以认为是剪枝操作
if (dp[i][target]) {
return true;
}
}
return dp[len - 1][target];
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/
来源:力扣(LeetCode)
这里可能会有人困惑为什么压缩到一维时,要采用逆序。因为在一维情况下,是根据
dp[j] || dp[j - nums[i]]
来推d[j]的值,如不逆序,就无法保证在外循环 i 值保持不变 j 值递增的情况下,dp[j - num[i]]的值不会被当前所放入的nums[i]所修改,当 j 值未到达临界条件前,会一直被nums[i]影响,也即是可能重复的放入了多次nums[i],为了避免前面对后面产生影响,故用逆序。
举个例子:
数组为[2,2,3,5],要找和为6的组合,i = 0时,dp[2]为真,当i自增到1,j = 4时,nums[i] = 2,dp[4] = dp[4] || dp[4 - 2]为true,当i不变,j = 6时,dp[6] = dp [6] || dp [6 - 2],而dp[4]为true,所以dp[6] = true,显然是错误的。 故必须得纠正在正序情况下,i值不变时多次放入nums[i]的情况。
参考代码3:只展示了使用一维表格,并且从后向前填表格的代码。
public class Solution {
public boolean canPartition(int[] nums) {
int len = nums.length;
int sum = 0;
for (int num : nums) {
sum += num;
}
if ((sum & 1) == 1) {
return false;
}
int target = sum / 2;
boolean[] dp = new boolean[target + 1];
dp[0] = true;
if (nums[0] <= target) {
dp[nums[0]] = true;
}
for (int i = 1; i < len; i++) {
for (int j = target; nums[i] <= j; j--) {
if (dp[target]) {
return true;
}
dp[j] = dp[j] || dp[j - nums[i]];
}
}
return dp[target];
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/
来源:力扣(LeetCode)
总结:
目标和(medium难度)(0-1背包问题)
https://leetcode-cn.com/problems/target-sum/
<方法一>:动态规划
递推公式的推导由来:
// 状态转移方程
dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]]
// 上面的式子等价于
dp[i][j] += dp [i-1][j-nums[i]]
dp[i][j] += dp [i-1][j+nums[i]]
// 可改写成
dp[i][j] = dp[i][j] +dp [i-1][j-nums[i]]
dp[i][j] = dp[i][j] +dp [i-1][j+nums[i]]
// 用 j = j + nums[i] 代入dp[i][j] = dp[i][j] +dp [i-1][j-nums[i]]得:
dp[i][j + nums[i]] = dp[i][j + nums[i]] + dp[i - 1][j]
// 用 j = j - nums[i] 代入dp[i][j] = dp[i][j] +dp [i-1][j+nums[i]]得:
dp[i][j - nums[i]] = dp[i][j - nums[i]] + dp[i - 1][j]
// 所以最终得到
dp[i][j + nums[i]] += dp[i - 1][j]
dp[i][j - nums[i]] += dp[i - 1][j]
这个其实可以理解为dp[i][x]这个位置,被更新过两次,一次是dp[i][a+nums[i]]的时候更新的,一次是dp[i][b-nums[i]]的时候更新的。a+nums[i]=b-nums[i]=j,所以才有这种自增操作。实际上和第一步状态转移方程是一致的。
dp[i][j]表示到第i个字符和为j的方法数。
dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]]
dp[i - 1][j - nums[i]] 表示这次是+时的方法数,
dp[i - 1][j + nums[i]] 表示这次是-时的方法数。
如果我们j正序遍历,把dp数组初始化为0.
则上面公式可以转化为:dp[i][j] == 0(计算前)
dp[i][j] = dp[i][j] + dp[i - 1][j - nums[i]]
dp[i][j] = dp[i][j] + dp[i - 1][j + nums[i]] ==>
遍历j时,我们利用上一次dp[i- 1][x]的计算结果,可以每次更新两个dp[i][x]的结果:
dp[i][j + nums[i]] = dp[i][j + nums[i]] + dp[i - 1][j]; 此时dp[i][j + nums[i]] = 0
dp[i][j - nums[i]] = dp[i][j - nums[i]] + dp[i - 1][j]; 此时dp[i][j - nums[i]] = 0 =>
编程如下形式:
dp[i][j + nums[i]] += dp[i - 1][j]
dp[i][j - nums[i]] += dp[i - 1][j]
这样,每次遍历j时,我们不计算dp[i][j]的值,而是利用dp[i - 1][j]的值,更新两个相关dp节点的值,会加速计算。
public class Solution {
public int findTargetSumWays(int[] nums, int S) {
int[][] dp = new int[nums.length][2001];
dp[0][nums[0] + 1000] = 1;
dp[0][-nums[0] + 1000] += 1;
for (int i = 1; i < nums.length; i++) {
for (int sum = -1000; sum <= 1000; sum++) {
if (dp[i - 1][sum + 1000] > 0) {
dp[i][sum + nums[i] + 1000] += dp[i - 1][sum + 1000];
dp[i][sum - nums[i] + 1000] += dp[i - 1][sum + 1000];
}
}
}
return S > 1000 ? 0 : dp[nums.length - 1][S + 1000];
}
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/target-sum/solution/mu-biao-he-by-leetcode/
来源:力扣(LeetCode)
代码解析:
第一步:
先创建一个二维数组表示dp表格,整个dp表格区域应该是分为三部分:-/0/+。那么对应的表格的每一行的长度 t 就可以表示为:t=(sum*2)+1,其中一个sum表示nums中执行全部执行加/减能达到的数。因为题目说的初始数组的和不会超过1000,所以我们把总和都加上1000防止数组索引有负数,正数加上1000最大就是2000了。在+1和-1中间还存在0,所以要在(sum*2)后面再+1,所以行数总共为2001行。所以数组的大小取的2001。具体表格如下图所示(下图sum为5):
第二步:
初始化:先对dp表格的第一行进行初始化,此时存在两种情况:
- nums[0] != 0:则在表格中对应+nums[0]和-nums[0]的位置应赋1,代表加减两种情况。
- nums[0] == 0:此时+nums[0]和-nums[0]均为0,则在dp[0][-nums[0]+1000]的位置赋2。
所以代码中的写法为:
dp[0][nums[0] + 1000] = 1; //[nums[0] + 1000]中的+1000仍表示整体右移防止出现负数造成数组越界。
dp[0][-nums[0] + 1000] += 1;
//等价于dp[0][-nums[0] + 1000] = dp[0][-nums[0] + 1000] + 1;
//这种累加的形式是为了在nums[0]==0时,在为+nums[0]对应的情况赋1了以后,
//保证-nums[0]对应的1种情况能够累加上去。这样才能使得dp[0][-nums[0]+1000]的位置为2
//写成 dp[0][nums[0] + 1000] += 1;dp[0][nums[0] + 1000] = 1;亦可。
第三步:
第一个for循环,i为有i个数相加。
第二个for循环,直接找所有sum的可能性,后面的if判断代表,只要dp[i - 1][sum + 1000] > 0;表示:前i-1个元素组成的和为sum的方案数不为0,即存在这种情况。
之后if里面的两个语句,就是本题的状态转移方程的递推形式。就是在本身基础上分别再加上:前i-1个元素组成的和为sum的方案数(dp[i - 1][sum + 1000])。所以用+=。
因为dp[i][x]这个位置,被更新过两次,一次是dp[i][a+nums[i]]的时候更新的,一次是dp[i][b-nums[i]]的时候更新的。a+nums[i]=b-nums[i]=j,所以才有这种自增操作。实际上和第一步状态转移方程是一致的。
结果:
返回值:若S>1000,超出所有元素的总和,返回0,否则:dp[i][j],i代表现在有多少数相加了,j代表相加数的总和,所以返回i = nums.length-1的时候,也就是所有数都用到加减的时候,j==S对应的方案数,即dp[nums.length - 1][S + 1000]的值。
<方法一>:动态规划+空间优化
public class Solution {
public int findTargetSumWays(int[] nums, int S) {
int[] dp = new int[2001];
dp[nums[0] + 1000] = 1;
dp[-nums[0] + 1000] += 1;
for (int i = 1; i < nums.length; i++) {
int[] next = new int[2001];
for (int sum = -1000; sum <= 1000; sum++) {
if (dp[sum + 1000] > 0) {
next[sum + nums[i] + 1000] += dp[sum + 1000];
next[sum - nums[i] + 1000] += dp[sum + 1000];
}
}
dp = next;
}
return S > 1000 ? 0 : dp[S + 1000];
}
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/target-sum/solution/mu-biao-he-by-leetcode/
来源:力扣(LeetCode)
另一道0-1背包问题:一和零(medium难度)