🌟欢迎来到 我的博客 —— 探索技术的无限可能!
5.不含连续1的非负整数
题目链接:600. 不含连续1的非负整数
给定一个正整数 n ,请你统计在 [0, n] 范围的非负整数中,有多少个整数的二进制表示中不存在 连续的 1 。
示例 1:
输入: n = 5
输出: 5
解释:
下面列出范围在 [0, 5] 的非负整数与其对应的二进制表示:
0 : 0
1 : 1
2 : 10
3 : 11
4 : 100
5 : 101
其中,只有整数 3 违反规则(有两个连续的 1 ),其他 5 个满足规则。
示例 2:
输入: n = 1
输出: 2
示例 3:
输入: n = 2
输出: 3
提示:
1 <= n <= 109
题解:
方法:数位 DP
class Solution {
public int findIntegers(int n) {
int m = Integer.SIZE - Integer.numberOfLeadingZeros(n);
int[][] memo = new int[m][2];
for (int[] row : memo) {
Arrays.fill(row, -1); // -1 表示没有计算过
}
return dfs(m - 1, 0, true, n, memo); // 从高位到低位
}
// pre 表示前一个比特位填的数
private int dfs(int i, int pre, boolean isLimit, int n, int[][] memo) {
if (i < 0) {
return 1;
}
if (!isLimit && memo[i][pre] >= 0) { // 之前计算过
return memo[i][pre];
}
int up = isLimit ? n >> i & 1 : 1;
int res = dfs(i - 1, 0, isLimit && up == 0, n, memo); // 填 0
if (pre == 0 && up == 1) { // 可以填 1
res += dfs(i - 1, 1, isLimit, n, memo); // 填 1
}
if (!isLimit) {
memo[i][pre] = res; // 记忆化
}
return res;
}
}
6.找出所有稳定的二进制数组 I
题目链接:3129. 找出所有稳定的二进制数组 I
给你 3 个正整数 zero ,one 和 limit 。
一个
二进制数组
arr 如果满足以下条件,那么我们称它是 稳定的 :
0 在 arr 中出现次数 恰好 为 zero 。
1 在 arr 中出现次数 恰好 为 one 。
arr 中每个长度超过 limit 的
子数组
都 同时 包含 0 和 1 。
请你返回 稳定 二进制数组的 总 数目。
由于答案可能很大,将它对 109 + 7 取余 后返回。
示例 1:
输入:zero = 1, one = 1, limit = 2
输出:2
解释:
两个稳定的二进制数组为 [1,0] 和 [0,1] ,两个数组都有一个 0 和一个 1 ,且没有子数组长度大于 2 。
示例 2:
输入:zero = 1, one = 2, limit = 1
输出:1
解释:
唯一稳定的二进制数组是 [1,0,1] 。
二进制数组 [1,1,0] 和 [0,1,1] 都有长度为 2 且元素全都相同的子数组,所以它们不稳定。
示例 3:
输入:zero = 3, one = 3, limit = 2
输出:14
解释:
所有稳定的二进制数组包括 [0,0,1,0,1,1] ,[0,0,1,1,0,1] ,[0,1,0,0,1,1]
,[0,1,0,1,0,1] ,[0,1,0,1,1,0] ,[0,1,1,0,0,1] ,[0,1,1,0,1,0]
,[1,0,0,1,0,1] ,[1,0,0,1,1,0] ,[1,0,1,0,0,1] ,[1,0,1,0,1,0]
,[1,0,1,1,0,0] ,[1,1,0,0,1,0] 和 [1,1,0,1,0,0] 。
提示:
1 <= zero, one, limit <= 200
题解:
方法:动态规划
class Solution {
public int numberOfStableArrays(int zero, int one, int limit) {
final int mod = (int) 1e9 + 7;
long[][][] f = new long[zero + 1][one + 1][2];
for (int i = 1; i <= Math.min(zero, limit); ++i) {
f[i][0][0] = 1;
}
for (int j = 1; j <= Math.min(one, limit); ++j) {
f[0][j][1] = 1;
}
for (int i = 1; i <= zero; ++i) {
for (int j = 1; j <= one; ++j) {
long x = i - limit - 1 < 0 ? 0 : f[i - limit - 1][j][1];
long y = j - limit - 1 < 0 ? 0 : f[i][j - limit - 1][0];
f[i][j][0] = (f[i - 1][j][0] + f[i - 1][j][1] - x + mod) % mod;
f[i][j][1] = (f[i][j - 1][0] + f[i][j - 1][1] - y + mod) % mod;
}
}
return (int) ((f[zero][one][0] + f[zero][one][1]) % mod);
}
}
7.找出所有稳定的二进制数组 II
给你 3 个正整数 zero ,one 和 limit 。
一个
二进制数组
arr 如果满足以下条件,那么我们称它是 稳定的 :
0 在 arr 中出现次数 恰好 为 zero 。
1 在 arr 中出现次数 恰好 为 one 。
arr 中每个长度超过 limit 的
子数组
都 同时 包含 0 和 1 。
请你返回 稳定 二进制数组的 总 数目。
由于答案可能很大,将它对 109 + 7 取余 后返回。
示例 1:
输入:zero = 1, one = 1, limit = 2
输出:2
解释:
两个稳定的二进制数组为 [1,0] 和 [0,1] ,两个数组都有一个 0 和一个 1 ,且没有子数组长度大于 2 。
示例 2:
输入:zero = 1, one = 2, limit = 1
输出:1
解释:
唯一稳定的二进制数组是 [1,0,1] 。
二进制数组 [1,1,0] 和 [0,1,1] 都有长度为 2 且元素全都相同的子数组,所以它们不稳定。
示例 3:
输入:zero = 3, one = 3, limit = 2
输出:14
解释:
所有稳定的二进制数组包括 [0,0,1,0,1,1] ,[0,0,1,1,0,1] ,[0,1,0,0,1,1]
,[0,1,0,1,0,1] ,[0,1,0,1,1,0] ,[0,1,1,0,0,1] ,[0,1,1,0,1,0]
,[1,0,0,1,0,1] ,[1,0,0,1,1,0] ,[1,0,1,0,0,1] ,[1,0,1,0,1,0]
,[1,0,1,1,0,0] ,[1,1,0,0,1,0] 和 [1,1,0,1,0,0] 。
提示:
1 <= zero, one, limit <= 1000
题解:
方法:递推
class Solution {
public int numberOfStableArrays(int zero, int one, int limit) {
final int MOD = 1_000_000_007;
int[][][] f = new int[zero + 1][one + 1][2];
for (int i = 1; i <= Math.min(limit, zero); i++) {
f[i][0][0] = 1;
}
for (int j = 1; j <= Math.min(limit, one); j++) {
f[0][j][1] = 1;
}
for (int i = 1; i <= zero; i++) {
for (int j = 1; j <= one; j++) {
// + MOD 保证答案非负
f[i][j][0] = (int) (((long) f[i - 1][j][0] + f[i - 1][j][1] + (i > limit ? MOD - f[i - limit - 1][j][1] : 0)) % MOD);
f[i][j][1] = (int) (((long) f[i][j - 1][0] + f[i][j - 1][1] + (j > limit ? MOD - f[i][j - limit - 1][0] : 0)) % MOD);
}
}
return (f[zero][one][0] + f[zero][one][1]) % MOD;
}
}
8.找出与数组相加的整数 I
题目链接:3131. 找出与数组相加的整数 I
给你两个长度相等的数组 nums1 和 nums2。
数组 nums1 中的每个元素都与变量 x 所表示的整数相加。如果 x 为负数,则表现为元素值的减少。
在与 x 相加后,nums1 和 nums2 相等 。当两个数组中包含相同的整数,并且这些整数出现的频次相同时,两个数组 相等 。
返回整数 x 。
示例 1:
输入:nums1 = [2,6,4], nums2 = [9,7,5]
输出:3
解释:
与 3 相加后,nums1 和 nums2 相等。
示例 2:
输入:nums1 = [10], nums2 = [5]
输出:-5
解释:
与 -5 相加后,nums1 和 nums2 相等。
示例 3:
输入:nums1 = [1,1,1,1], nums2 = [1,1,1,1]
输出:0
解释:
与 0 相加后,nums1 和 nums2 相等。
提示:
1 <= nums1.length == nums2.length <= 100
0 <= nums1[i], nums2[i] <= 1000
测试用例以这样的方式生成:存在一个整数 x,使得 nums1 中的每个元素都与 x 相加后,nums1 与 nums2 相等。
题解:
方法:数学
class Solution {
public int addedInteger(int[] nums1, int[] nums2) {
return min(nums2) - min(nums1);
}
private int min(int[] nums) {
int res = Integer.MAX_VALUE;
for (int x : nums) {
res = Math.min(res, x);
}
return res;
}
}
9.找出与数组相加的整数 II
题目链接:3132. 找出与数组相加的整数 II
给你两个整数数组 nums1 和 nums2。
从 nums1 中移除两个元素,并且所有其他元素都与变量 x 所表示的整数相加。如果 x 为负数,则表现为元素值的减少。
执行上述操作后,nums1 和 nums2 相等 。当两个数组中包含相同的整数,并且这些整数出现的频次相同时,两个数组 相等 。
返回能够实现数组相等的 最小 整数 x 。
示例 1:
输入:nums1 = [4,20,16,12,8], nums2 = [14,18,10]
输出:-2
解释:
移除 nums1 中下标为 [0,4] 的两个元素,并且每个元素与 -2 相加后,nums1 变为 [18,14,10] ,与 nums2
相等。
示例 2:
输入:nums1 = [3,5,5,3], nums2 = [7,7]
输出:2
解释:
移除 nums1 中下标为 [0,3] 的两个元素,并且每个元素与 2 相加后,nums1 变为 [7,7] ,与 nums2 相等。
提示:
3 <= nums1.length <= 200
nums2.length == nums1.length - 2
0 <= nums1[i], nums2[i] <= 1000
测试用例以这样的方式生成:存在一个整数 x,nums1 中的每个元素都与 x 相加后,再移除两个元素,nums1 可以与 nums2 相等。
题解:
方法:O(nlogn) 排序+判断子序列
class Solution {
public int minimumAddedInteger(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
// 枚举保留 nums1[2] 或者 nums1[1] 或者 nums1[0]
// 倒着枚举是因为 nums1[i] 越大答案越小,第一个满足的就是答案
for (int i = 2; i > 0; i--) {
int x = nums2[0] - nums1[i];
// 在 {nums1[i] + x} 中找子序列 nums2
int j = 0;
for (int k = i; k < nums1.length; k++) {
if (nums2[j] == nums1[k] + x && ++j == nums2.length) {
// nums2 是 {nums1[i] + x} 的子序列
return x;
}
}
}
// 题目保证答案一定存在
return nums2[0] - nums1[0];
}
}
10.找到 Alice 和 Bob 可以相遇的建筑
题目链接:2940. 找到 Alice 和 Bob 可以相遇的建筑
给你一个下标从 0 开始的正整数数组 heights ,其中 heights[i] 表示第 i 栋建筑的高度。
如果一个人在建筑 i ,且存在 i < j 的建筑 j 满足 heights[i] < heights[j] ,那么这个人可以移动到建筑 j 。
给你另外一个数组 queries ,其中 queries[i] = [ai, bi] 。第 i 个查询中,Alice 在建筑 ai ,Bob 在建筑 bi 。
请你能返回一个数组 ans ,其中 ans[i] 是第 i 个查询中,Alice 和 Bob 可以相遇的 最左边的建筑 。如果对于查询 i ,Alice 和 Bob 不能相遇,令 ans[i] 为 -1 。
示例 1:
输入:heights = [6,4,8,5,2,7], queries = [[0,1],[0,3],[2,4],[3,4],[2,2]]
输出:[2,5,-1,5,2]
解释:第一个查询中,Alice 和 Bob 可以移动到建筑 2 ,因为 heights[0] < heights[2] 且
heights[1] < heights[2] 。第二个查询中,Alice 和 Bob 可以移动到建筑 5 ,因为 heights[0] < heights[5] 且 heights[3]
< heights[5] 。第三个查询中,Alice 无法与 Bob 相遇,因为 Alice 不能移动到任何其他建筑。
第四个查询中,Alice 和 Bob 可以移动到建筑 5 ,因为 heights[3] < heights[5] 且 heights[4]
< heights[5] 。第五个查询中,Alice 和 Bob 已经在同一栋建筑中。
对于 ans[i] != -1 ,ans[i] 是 Alice 和 Bob 可以相遇的建筑中最左边建筑的下标。
对于 ans[i] == -1 ,不存在 Alice 和 Bob 可以相遇的建筑。
示例 2:
输入:heights = [5,3,8,2,6,1,4,6], queries =
[[0,7],[3,5],[5,2],[3,0],[1,6]]输出:[7,6,-1,4,6]
解释:第一个查询中,Alice 可以直接移动到 Bob 的建筑,因为 heights[0] < heights[7] 。
第二个查询中,Alice 和 Bob 可以移动到建筑 6 ,因为 heights[3] < heights[6] 且 heights[5]
< heights[6] 。第三个查询中,Alice 无法与 Bob 相遇,因为 Bob 不能移动到任何其他建筑。
第四个查询中,Alice 和 Bob 可以移动到建筑 4 ,因为 heights[3] < heights[4] 且 heights[0]
< heights[4] 。第五个查询中,Alice 可以直接移动到 Bob 的建筑,因为 heights[1] < heights[6] 。
对于 ans[i] != -1 ,ans[i] 是 Alice 和 Bob 可以相遇的建筑中最左边建筑的下标。
对于 ans[i] == -1 ,不存在 Alice 和 Bob 可以相遇的建筑。
提示:
1 <= heights.length <= 5 * 104
1 <= heights[i] <= 109
1 <= queries.length <= 5 * 104
queries[i] = [ai, bi]
0 <= ai, bi <= heights.length - 1
题解:
方法1:离线+最小堆
class Solution {
public int[] leftmostBuildingQueries(int[] heights, int[][] queries) {
int[] ans = new int[queries.length];
Arrays.fill(ans, -1);
List<int[]>[] qs = new ArrayList[heights.length];
Arrays.setAll(qs, i -> new ArrayList<>());
for (int i = 0; i < queries.length; i++) {
int a = queries[i][0];
int b = queries[i][1];
if (a > b) {
int tmp = a;
a = b;
b = tmp; // 保证 a <= b
}
if (a == b || heights[a] < heights[b]) {
ans[i] = b; // a 直接跳到 b
} else {
qs[b].add(new int[]{heights[a], i}); // 离线询问
}
}
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
for (int i = 0; i < heights.length; i++) {
while (!pq.isEmpty() && pq.peek()[0] < heights[i]) {
// 堆顶的 heights[a] 可以跳到 heights[i]
ans[pq.poll()[1]] = i;
}
for (int[] q : qs[i]) {
pq.offer(q); // 后面再回答
}
}
return ans;
}
}
方法2:离线+单调栈二分
class Solution {
public int[] leftmostBuildingQueries(int[] heights, int[][] queries) {
int n = heights.length;
int[] ans = new int[queries.length];
List<int[]>[] qs = new ArrayList[n];
Arrays.setAll(qs, i -> new ArrayList<>());
for (int i = 0; i < queries.length; i++) {
int a = queries[i][0];
int b = queries[i][1];
if (a > b) {
int tmp = a;
a = b;
b = tmp; // 保证 a <= b
}
if (a == b || heights[a] < heights[b]) {
ans[i] = b; // a 直接跳到 b
} else {
qs[b].add(new int[]{heights[a], i}); // 离线询问
}
}
int[] st = new int[n];
int top = 0;
for (int i = n - 1; i >= 0; i--) {
for (int[] q : qs[i]) {
ans[q[1]] = binarySearch(heights, st, top, q[0]);
}
while (top > 0 && heights[i] >= heights[st[top - 1]]) {
top--;
}
st[top++] = i;
}
return ans;
}
// 返回 st 中最后一个 > x 的高度的下标
// 如果不存在,返回 -1
// https://www.bilibili.com/video/BV1AP41137w7/
private int binarySearch(int[] heights, int[] st, int right, int x) {
int left = -1; // 开区间 (left, right)
while (left + 1 < right) { // 开区间不为空
int mid = (left + right) >>> 1;
if (heights[st[mid]] > x) {
left = mid; // 范围缩小到 (mid, right)
} else {
right = mid; // 范围缩小到 (left, mid)
}
}
return left < 0 ? -1 : st[left];
}
}
方法3:在线+线段树二分
class Solution {
public int[] leftmostBuildingQueries(int[] heights, int[][] queries) {
int n = heights.length;
mx = new int[2 << (Integer.SIZE - Integer.numberOfLeadingZeros(n))];
build(1, 0, n - 1, heights);
int[] ans = new int[queries.length];
for (int i = 0; i < queries.length; i++) {
int a = queries[i][0];
int b = queries[i][1];
if (a > b) {
int tmp = a;
a = b;
b = tmp; // 保证 a <= b
}
if (a == b || heights[a] < heights[b]) {
ans[i] = b; // a 直接跳到 b
} else {
// 线段树二分,找 [b+1,n-1] 中的第一个 > heights[a] 的位置
ans[i] = query(1, 0, n - 1, b + 1, heights[a]);
}
}
return ans;
}
private int[] mx;
// 用 heights 初始化线段树,维护区间最大值
private void build(int o, int l, int r, int[] heights) {
if (l == r) {
mx[o] = heights[l];
return;
}
int m = (l + r) / 2;
build(o * 2, l, m, heights);
build(o * 2 + 1, m + 1, r, heights);
mx[o] = Math.max(mx[o * 2], mx[o * 2 + 1]);
}
// 返回 [L,n-1] 中第一个 > v 的值的下标
// 如果不存在,返回 -1
private int query(int o, int l, int r, int L, int v) {
if (mx[o] <= v) { // 区间最大值 <= v
return -1; // 没有 > v 的数
}
if (l == r) { // 找到了
return l;
}
int m = (l + r) / 2;
if (L <= m) {
int pos = query(o * 2, l, m, L, v); // 递归左子树
if (pos >= 0) { // 找到了
return pos;
}
}
return query(o * 2 + 1, m + 1, r, L, v); // 递归右子树
}
}
11.不相交的线
题目链接:1035. 不相交的线
在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。
现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足:
nums1[i] == nums2[j]
且绘制的直线不与任何其他连线(非水平线)相交。
请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。
以这种方法绘制线条,并返回可以绘制的最大连线数。
示例 1:
输入:nums1 = [1,4,2], nums2 = [1,2,4]
输出:2
解释:可以画出两条不交叉的线,如上图所示。
但无法画出第三条不相交的直线,因为从 nums1[1]=4 到 nums2[2]=4 的直线将与从 nums1[2]=2 到
nums2[1]=2 的直线相交。
示例 2:
输入:nums1 = [2,5,1,2,5], nums2 = [10,5,2,1,5,2]
输出:3
示例 3:
输入:nums1 = [1,3,7,1,7,5], nums2 = [1,9,2,5,1]
输出:2
提示:
1 <= nums1.length, nums2.length <= 500
1 <= nums1[i], nums2[j] <= 2000
题解:
方法:动态规划
class Solution {
public int maxUncrossedLines(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
int[][] f = new int[m + 1][n + 1];
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (nums1[i - 1] == nums2[j - 1]) {
f[i][j] = f[i - 1][j - 1] + 1;
} else {
f[i][j] = Math.max(f[i - 1][j], f[i][j - 1]);
}
}
}
return f[m][n];
}
}