- 区域和检索 - 数组不可变
给定一个整数数组 nums,求出数组从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点。
输入:
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]
要计算 sumRange(i,j),则需要计算数组 nums 在下标 jj 和下标i−1 的前缀和,然后计算两个前缀和的差。
如果可以在初始化的时候计算出数组 nums 在每个下标处的前缀和,即可满足每次调用 sumRange 的时间复杂度都是 O(1)。
具体实现方面,假设数组 nums 的长度为 n,创建长度为n+1 的前缀和数组 sums,对于 00≤i<n 都有sums[i+1]=sums[i]+nums[i],则当 0<i≤n 时,sums[i] 表示数组 nums 从下标 0 到下标i−1 的前缀和。
将前缀和数组sums 的长度设为n+1 的目的是为了方便计算 sumRange(i,j),不需要对 i=0 的情况特殊处理。此时有:
sumRange(i,j)=sums[j+1]−sums[i]
思路
int[] nums = new int[10];
public NumArray(int[] nums) {
this.nums = nums;
}
public int sumRange(int i, int j) {
int sum =0;
for(int k =i;k <= j;k++){
sum += nums[i];
}
return sum;
}
class NumArray {
int[] sums;
public NumArray(int[] nums) {
int n = nums.length;
sums = new int[n + 1];
for (int i = 0; i < n; i++) {
sums[i + 1] = sums[i] + nums[i];
}
}
public int sumRange(int i, int j) {
return sums[j + 1] - sums[i];
}
}
- 使用最小花费爬楼梯
数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。
每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。
请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。
示例 1:
输入:cost = [10, 15, 20]
输出:15
解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。
示例 2:
输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出:6
解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6 。
假设数组 cost 的长度为 n,则 n 个阶梯分别对应下标 0 到 n−1,楼层顶部对应下标 n,问题等价于计算达到下标 n的最小花费。可以通过动态规划求解。
创建长度为 n+1 的数组dp,其中dp[i] 表示达到下标 i 的最小花费。
由于可以选择下标 0 或 1 作为初始阶梯,因此有 dp[0]=dp[1]=0。
当 2≤i≤n 时,可以从下标i−1 使用cost[i−1] 的花费达到下标 ii,或者从下标i−2 使用 cost[i−2] 的花费达到下标 i。为了使总花费最小,dp[i] 应取上述两项的最小值,因此状态转移方程如下:
dp[i]=min(dp[i−1]+cost[i−1],dp[i−2]+cost[i−2])
依次计算dp 中的每一项的值,最终得到的 dp[n] 即为达到楼层顶部的最小花费。
class Solution {
public int minCostClimbingStairs(int[] cost) {
int n = cost.length;
int[] dp = new int[n + 1];
dp[0] = dp[1] = 0;
for (int i = 2; i <= n; i++) {
dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
}
return dp[n];
}
}
- 除数博弈
爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。
最初,黑板上有一个数字 N 。在每个玩家的回合,玩家需要执行以下操作:
选出任一 x,满足 0 < x < N 且 N % x == 0 。
用 N - x 替换黑板上的数字 N 。
如果玩家无法执行这些操作,就会输掉游戏。
只有在爱丽丝在游戏中取得胜利时才返回 True,否则返回 False。假设两个玩家都以最佳状态参与游戏。
示例 1:
输入:2
输出:true
示例 2:
输入:3
输出:false
思路
public boolean divisorGame(int N) {
int count =1;
for(int i=0;i< N;i++){
if( N % i ==0){
N -=i;
count++;
}
}
if(count %2 ==0) return false;
return true;
}
class Solution {
public boolean divisorGame(int N) {
boolean[] f = new boolean[N + 5];
f[1] = false;
f[2] = true;
for (int i = 3; i <= N; ++i) {
for (int j = 1; j < i; ++j) {
if ((i % j) == 0 && !f[i - j]) {
f[i] = true;
break;
}
}
}
return f[N];
}
}
剑指 Offer 42. 连续子数组的最大和
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
状态定义: 设动态规划列表 dpdp ,dp[i]dp[i] 代表以元素 nums[i]nums[i] 为结尾的连续子数组最大和。
为何定义最大和 dp[i]dp[i] 中必须包含元素 nums[i]nums[i] :保证 dp[i]dp[i] 递推到 dp[i+1]dp[i+1] 的正确性;如果不包含 nums[i]nums[i] ,递推时则不满足题目的 连续子数组 要求。
转移方程: 若dp[i−1]≤0 ,说明 dp[i - 1]dp[i−1] 对 dp[i] 产生负贡献,即 dp[i−1]+nums[i] 还不如 nums[i] 本身大。
当 dp[i−1]>0 时:执行 dp[i]=dp[i−1]+nums[i] ;
当 dp[i−1]≤0 时:执行 dp[i] = nums[i]dp[i]=nums[i] ;
初始状态:dp[0]=nums[0],即以 nums[0] 结尾的连续子数组最大和为 nums[0] 。
返回值: 返回 dpdp 列表中的最大值,代表全局最大值。
思路
public int maxSubArray(int[] nums) {
int i=0,count =0;
for(int j=1;j< nums.length;j++){
int sum = nums[i] + nums[j];
if(sum < count){
i++;
count = sum;
}
}
return count;
}
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;
}
}
public int maxSubArray(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = nums[0];
int res =dp[0];
for(int i=1;i< nums.length; i++){
if(dp[i-1] > 0){
dp[i] = dp[i-1] + nums[i];
}else{
dp[i] = nums[i];
}
res = Math.max(res,dp[i]);
}
return res;
}