问题3: 给你一个长度为 n 的整数数组 nums 和一个相同长度的二进制字符串 s。 一开始,...
Created on: December 20, 2025
Answered using GPT-5.1 Thinking by Chat01
Created on: December 20, 2025
Answered using GPT-5.1 Thinking by Chat01
问题3:
给你一个长度为 n 的整数数组 nums 和一个相同长度的二进制字符串 s。
一开始,你的分数为 0。对于每一个 s[i] = '1' 的下标 i,都会为分数贡献 nums[i]。
你可以执行 任意 次操作(包括零次)。在一次操作中,你可以选择一个下标 i(0 <= i < n - 1),满足s[i] = '0' 且 s[i + 1] = '1',并交换这两个字符。
返回一个整数,表示你可以获得的 最大可能分数。
示例 1:
输入: nums = [2,1,5,2,3], s = "01010"
输出: 7
解释:
我们可以执行以下交换操作:
在下标 i = 0 处交换:"01010" 变为 "10010"
在下标 i = 2 处交换:"10010" 变为 "10100"
下标 0 和 2 包含 '1',贡献的分数为 nums[0] + nums[2] = 2 + 5 = 7。这是可以获得的最大分数。
示例 2:
输入: nums = [4,7,2,9], s = "0000"
输出: 0
解释:
字符串 s 中没有字符 '1',因此无法执行交换操作。分数保持为 0。
提示:
n == nums.length == s.length
1 <= n <= 10^5
1 <= nums[i] <= 10^9
s[i] 是 '0' 或 '1'
class Solution {
public long maximumScore(int[] nums, String s) {
}
}
问题4:
给你一个整数 n。
我们将 1 到 n 的整数按从左到右的顺序排成一个序列。然后,交替地执行以下两种操作,直到只剩下一个整数为止,从操作 1 开始:
操作 1:从左侧开始,隔一个数删除一个数。
操作 2:从右侧开始,隔一个数删除一个数。
返回最后剩下的那个整数。
示例 1:
输入: n = 8
输出: 3
解释:
写下序列 [1, 2, 3, 4, 5, 6, 7, 8]。
从左侧开始,我们删除每隔一个数字:[1, 2, 3, 4, 5, 6, 7, 8]。剩下的整数是 [1, 3, 5, 7]。
从右侧开始,我们删除每隔一个数字:[1, 3, 5, 7]。剩下的整数是 [3, 7]。
从左侧开始,我们删除每隔一个数字:[3, 7]。剩下的整数是 [3]。
示例 2:
输入: n = 5
输出: 1
解释:
写下序列 [1, 2, 3, 4, 5]。
从左侧开始,我们删除每隔一个数字:[1, 2, 3, 4, 5]。剩下的整数是 [1, 3, 5]。
从右侧开始,我们删除每隔一个数字:[1, 3, 5]。剩下的整数是 [1, 5]。
从左侧开始,我们删除每隔一个数字:[1, 5]。剩下的整数是 [1]。
示例 3:
输入: n = 1
输出: 1
解释:
写下序列 [1]。
最后剩下的整数是 1。
提示:
1 <= n <= 10^15
class Solution {
public long lastInteger(long n) {
}
} 解决这2个问题,通过所有例子,并且不能超时
先直接给代码,然后简单讲思路 😄
'1' 所在位置的 nums[i] 之和,但是我们要找的是交换之后的最大得分。'1' 不断往左移动(通过 01 -> 10 的交换),但不能往右移动。'1',使得:
'1' 的个数 = m;[i..n-1],这个后缀中最终放的 '1' 个数,不能超过原串中这个后缀里本来就有的 '1' 个数(因为 '1' 不能向右跑到比自己原来更靠右的位置)。suf[i] = 原串中从 i 到末尾 '1' 的个数,那么对每个后缀 [i..n-1],我们最多可以选择 suf[i] 个位置放 '1'。i 当成一个“候选位置”,放入一个小根堆(只存 nums[i]),表示“如果将来要在后缀 [i..n-1] 放 '1',可以考虑这个位置”。suf[i];如果堆里元素个数 > suf[i],说明选的位置超了,只好把堆中 最小的那个 nums 丢掉(这样总和最大)。'1' 的那些位置,堆中元素之和就是最大得分。复杂度:每个位置进堆一次、最多被弹出一次,O(n log n),n ≤ 1e5 完全没问题。
javaimport java.util.PriorityQueue; class Solution { public long maximumScore(int[] nums, String s) { int n = nums.length; // suf[i] 表示从 i 到末尾的 '1' 的个数 int[] suf = new int[n + 1]; int cnt = 0; for (int i = n - 1; i >= 0; --i) { if (s.charAt(i) == '1') cnt++; suf[i] = cnt; } PriorityQueue<Integer> minHeap = new PriorityQueue<>(); // 存当前后缀选择的位置的 nums 值,维护最大和 long sum = 0L; // 从右往左扫 for (int i = n - 1; i >= 0; --i) { // 把位置 i 作为候选放进来 minHeap.offer(nums[i]); sum += nums[i]; int cap = suf[i]; // 这个后缀最多可以有 cap 个 '1' // 超出容量,则删掉最小的一个位置(不在这里放 1) while (minHeap.size() > cap) { sum -= minHeap.poll(); } } return sum; } }
规则总结一下:
[1, 2, 3, ..., n]。直接模拟肯定超时(n 到 1e15)。我们用“等差数列 + 头、步长”的经典写法来做:
用三个变量描述当前序列(不真的存数组):
head:当前序列的第一个数;step:相邻两个数之间的间隔(公差);remain:当前还剩多少个数;left:下一次从左还是从右执行。初始:
head = 1, step = 1, remain = n, left = true。当前序列:head, head+step, head+2*step, ... 共 remain 个。
head。ceil(remain / 2)。step *= 2。当前还有 remain 个数。
从右保留从右数第 1、3、5、... 个。用索引分析:
remain 是奇数:保留下来的索引是 0,2,4,...(与从左类似),所以 head 不变。remain 是偶数:保留下来的是 1,3,5,...,所以新的 head = head + step。ceil(remain / 2),步长同样乘 2。每轮操作后,remain 至少减半,所以循环次数是 O(log n)。对于 n <= 1e15 非常轻松。
javaclass Solution { public long lastInteger(long n) { long head = 1L; // 当前序列的第一个数 long step = 1L; // 相邻数字的间隔 long remain = n; // 当前还剩多少个数 boolean left = true; // 下一轮是否从左开始 while (remain > 1) { if (left) { // 从左边开始:保留索引 0,2,4,... remain = (remain + 1) / 2; // ceil(remain / 2) step <<= 1; // step *= 2 left = false; } else { // 从右边开始 if ((remain & 1L) == 1L) { // remain 为奇数,保留索引 0,2,4,... => head 不变 remain = remain / 2 + 1; } else { // remain 为偶数,保留索引 1,3,5,... => head 向右移一个 step head += step; remain /= 2; } step <<= 1; // step *= 2 left = true; } } return head; } }
这两个解法:
O(n log n),n <= 1e5 不会超时;O(log n),n <= 1e15 完全没压力。如果你想,我也可以把两个题都写在一个文件里,或者再给你推几组随机样例验证一下。