Q1、二进制间距
1、题目描述
给定一个正整数 n
,找到并返回 n
的二进制表示中两个 相邻 1 之间的 最长距离 。如果不存在两个相邻的 1,返回 0
。
如果只有 0
将两个 1
分隔开(可能不存在 0
),则认为这两个 1 彼此 相邻 。两个 1
之间的距离是它们的二进制表示中位置的绝对差。例如,"1001"
中的两个 1
的距离为 3 。
示例 1:
输入:n = 22 输出:2 解释:22 的二进制是 "10110" 。 在 22 的二进制表示中,有三个 1,组成两对相邻的 1 。 第一对相邻的 1 中,两个 1 之间的距离为 2 。 第二对相邻的 1 中,两个 1 之间的距离为 1 。 答案取两个距离之中最大的,也就是 2 。
示例 2:
输入:n = 8 输出:0 解释:8 的二进制是 "1000" 。 在 8 的二进制表示中没有相邻的两个 1,所以返回 0 。
示例 3:
输入:n = 5 输出:2 解释:5 的二进制是 "101" 。
提示:
1 <= n <= 109
2、解题思路
- 遍历二进制位 :从最低位到最高位逐位检查
n
的二进制表示。 - 记录
1
的位置 :用一个变量last
记录上一个1
的位置。 - 计算距离 :每遇到一个
1
,计算当前1
和上一个1
的距离,并更新最大值。 - 返回结果:最后返回最大的距离。
3、代码实现
C++
c++
class Solution {
public:
int binaryGap(int n) {
int last = -1; // 记录上一个 1 的位置
int ans = 0; // 记录最大距离
for (int i = 0; n; ++i) {
if (n & 1) // 检查当前位是否为 1
{
if (last != -1) // 如果之前已经有 1
{
ans = max(ans, i - last); // 计算距离并更新最大值
}
last = i; // 更新上一个 1 的位置
}
n >>= 1; // 右移一位, 检查下一位
}
return ans;
}
};
Java
java
class Solution {
public int binaryGap(int n) {
int last = -1; // 记录上一个 1 的位置
int ans = 0; // 记录最大距离
for (int i = 0; n != 0; ++i) {
if ((n & 1) == 1) { // 检查当前位是否为 1
if (last != -1) { // 如果之前已经有 1
ans = Math.max(ans, i - last); // 计算距离并更新最大值
}
last = i; // 更新上一个 1 的位置
}
n >>= 1; // 右移一位,检查下一位
}
return ans;
}
}
Python
python
class Solution:
def binaryGap(self, n: int) -> int:
last = -1 # 记录上一个 1 的位置
ans = 0 # 记录最大距离
i = 0
while n:
if n & 1: # 检查当前位是否为 1
if last != -1: # 如果之前已经有 1
ans = max(ans, i - last) # 计算距离并更新最大值
last = i # 更新上一个 1 的位置
n >>= 1 # 右移一位,检查下一位
i += 1
return ans
python
class Solution:
def binaryGap(self, n: int) -> int:
binary = bin(n)[2:] # 转换为二进制字符串
last = -1 # 记录上一个 1 的位置
ans = 0 # 记录最大距离
for i, bit in enumerate(binary):
if bit == '1':
if last != -1: # 如果之前已经有 1
ans = max(ans, i - last) # 计算距离并更新最大值
last = i # 更新上一个 1 的位置
return ans
4、复杂度分析
- 时间复杂度 :
O(log n)
,因为需要遍历n
的二进制位数。 - 空间复杂度 :
O(1)
,只使用了常数空间。
Q2、重新排序得到 2 的幂
1、题目描述
给定正整数 n
,我们按任何顺序(包括原始顺序)将数字重新排序,注意其前导数字不能为零。
如果我们可以通过上述方式得到 2 的幂,返回 true
;否则,返回 false
。
示例 1:
输入:n = 1 输出:true
示例 2:
输入:n = 10 输出:false
提示:
1 <= n <= 109
2、解题思路
方法一:回溯法
- 生成所有排列 :将
n
的数字重新排列,检查是否有排列是 2 的幂。 - 避免重复计算:排序数字并跳过重复数字的递归分支。
- 检查 2 的幂 :使用
n & (n - 1) == 0
判断是否为 2 的幂。 - 优化:剪枝(跳过前导零和重复数字)。
方法二:数字频率统计
- 预计算所有 2 的幂的数字频率:生成所有 2 的幂(1 到 1e9),统计每个数字的频率。
- 比较频率 :统计
n
的数字频率,检查是否与某个 2 的幂的频率匹配。 - 优化:使用哈希表存储所有 2 的幂的数字频率。
3、代码实现
C++
c++
// 方法1: 回溯
class Solution {
private:
vector<int> vis; // 标记数字是否被使用
bool isPowerOfTwo(int n) {
return (n & (n - 1)) == 0; // 判断是否为 2 的幂
}
bool backtrack(string& nums, int idx, int num) {
if (idx == nums.length()) {
return isPowerOfTwo(num); // 检查当前数字是否为 2 的幂
}
for (int i = 0; i < nums.length(); ++i) {
// 跳过前导零、已使用的数字和重复数字
if ((num == 0 && nums[i] == '0') || vis[i] ||
(i > 0 && !vis[i - 1] && nums[i] == nums[i - 1])) {
continue;
}
vis[i] = 1; // 标记为已使用
if (backtrack(nums, idx + 1, num * 10 + (nums[i] - '0'))) {
return true; // 找到有效排列
}
vis[i] = 0; // 回溯
}
return false;
}
public:
bool reorderedPowerOf2(int n) {
string nums = to_string(n);
sort(nums.begin(), nums.end()); // 排序以便跳过重复数字
vis.resize(nums.length());
return backtrack(nums, 0, 0);
}
};
c++
// 方法2: 数据频率统计
string countDigits(int n) {
string cnt(10, 0); // 统计数字 0-9 的出现次数
while (n) {
++cnt[n % 10];
n /= 10;
}
return cnt;
}
unordered_set<string> powerOf2Digits; // 存储所有 2 的幂的数字频率
int init = []() {
for (int n = 1; n <= 1e9; n <<= 1) // 预计所有 2 的幂
{
powerOf2Digits.insert(countDigits(n));
}
return 0;
}();
class Solution {
public:
bool reorderedPowerOf2(int n) {
return powerOf2Digits.count(countDigits(n)); // 检查是否匹配
}
};
Java
java
// 方法1: 回溯
class Solution {
private boolean[] vis;
private boolean isPowerOfTwo(int n) {
return (n & (n - 1)) == 0;
}
private boolean backtrack(char[] nums, int idx, int num) {
if (idx == nums.length) {
return isPowerOfTwo(num);
}
for (int i = 0; i < nums.length; ++i) {
if ((num == 0 && nums[i] == '0') || vis[i] || (i > 0 && !vis[i - 1] && nums[i] == nums[i - 1])) {
continue;
}
vis[i] = true;
if (backtrack(nums, idx + 1, num * 10 + (nums[i] - '0'))) {
return true;
}
vis[i] = false;
}
return false;
}
public boolean reorderedPowerOf2(int n) {
char[] nums = String.valueOf(n).toCharArray();
Arrays.sort(nums);
vis = new boolean[nums.length];
return backtrack(nums, 0, 0);
}
}
python
// 方法2: 数据频率统计
class Solution {
private String countDigits(int n) {
int[] cnt = new int[10];
while (n > 0) {
++cnt[n % 10];
n /= 10;
}
return Arrays.toString(cnt);
}
private static final Set<String> powerOf2Digits = new HashSet<>();
static {
for (int n = 1; n <= 1e9; n <<= 1) {
powerOf2Digits.add(new Solution().countDigits(n));
}
}
public boolean reorderedPowerOf2(int n) {
return powerOf2Digits.contains(countDigits(n));
}
}
Python
python
# 方法1: 回溯
class Solution:
def reorderedPowerOf2(self, n: int) -> bool:
nums = sorted(str(n)) # 排序以便跳过重复数字
vis = [False] * len(nums)
def backtrack(idx: int, num: int) -> bool:
if idx == len(nums):
return (num & (num - 1)) == 0 # 判断是否为 2 的幂
for i in range(len(nums)):
if (num == 0 and nums[i] == '0') or vis[i] or \
(i > 0 and not vis[i - 1] and nums[i] == nums[i - 1]):
continue
vis[i] = True
if backtrack(idx + 1, num * 10 + int(nums[i])):
return True
vis[i] = False
return False
return backtrack(0, 0)
python
# 方法2: 数据频率统计
class Solution:
def reorderedPowerOf2(self, n: int) -> bool:
def countDigits(n: int) -> str:
cnt = [0] * 10
while n:
cnt[n % 10] += 1
n //= 10
return str(cnt)
powerOf2Digits = {countDigits(1 << i) for i in range(30)} # 预计算所有 2 的幂
return countDigits(n) in powerOf2Digits
4、复杂度分析
- 方法一 :
- 时间复杂度 :
O(m!)
,m
是n
的位数(最坏情况)。 - 空间复杂度 :
O(m)
,递归栈和标记数组。
- 时间复杂度 :
- 方法二 :
- 时间复杂度 :
O(1)
,预计算后查询是常数时间。 - 空间复杂度 :
O(1)
,预计算存储所有 2 的幂的数字频率。
- 时间复杂度 :
Q3、优势洗牌
1、题目描述
给定两个长度相等的数组 nums1
和 nums2
,nums1
相对于 nums2
的优势 可以用满足 nums1[i] > nums2[i]
的索引 i
的数目来描述。
返回 nums1
的 任意 排列,使其相对于 nums2
的优势最大化。
示例 1:
输入:nums1 = [2,7,11,15], nums2 = [1,10,4,11] 输出:[2,11,7,15]
示例 2:
输入:nums1 = [12,24,8,32], nums2 = [13,25,32,11] 输出:[24,32,8,12]
提示:
1 <= nums1.length <= 105
nums2.length == nums1.length
0 <= nums1[i], nums2[i] <= 109
2、解题思路
-
排序并保留原始索引:
- 对
nums1
和nums2
进行排序,但需要保留原始索引以便最后放置结果。
- 对
-
贪心匹配:
- 使用双指针,尝试用
nums1
的最小值去匹配nums2
的最小值。 - 如果
nums1
的最小值大于nums2
的最小值,则直接匹配。 - 否则,用
nums1
的最小值去"消耗"nums2
的最大值(因为无法战胜任何其他数)。
- 使用双指针,尝试用
3、代码实现
C++
c++
class Solution {
public:
vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
vector<int> idx1(n), idx2(n);
iota(idx1.begin(), idx1.end(), 0); // 初始化索引 0, 1, 2, ..., n-1
iota(idx2.begin(), idx2.end(), 0);
// 根据 nums1 和 nums2 的值排序索引
sort(idx1.begin(), idx1.end(), [&](int i, int j) { return nums1[i] < nums1[j]; });
sort(idx2.begin(), idx2.end(), [&](int i, int j) { return nums2[i] < nums2[j]; });
vector<int> ans(n);
int left = 0, right = n - 1;
for (int i = 0; i < n; ++i) {
if (nums1[idx1[i]] > nums2[idx2[left]]) {
// 如果 nums1 当前最小值能战胜 nums2 的最小值, 则直接匹配
ans[idx2[left]] = nums1[idx1[i]];
++left; // 移动 nums2 的指针
} else {
// 否则, 用 nums1 的最小值去 "消耗" nums2 的最大值
ans[idx2[right]] = nums1[idx1[i]];
--right; // 移动 nums2 的指针
}
}
return ans;
}
};
Java
java
class Solution {
public int[] advantageCount(int[] nums1, int[] nums2) {
int n = nums1.length;
Integer[] idx1 = IntStream.range(0, n).boxed().toArray(Integer[]::new);
Integer[] idx2 = IntStream.range(0, n).boxed().toArray(Integer[]::new);
// 根据 nums1 和 nums2 的值排序索引
Arrays.sort(idx1, (i, j) -> nums1[i] - nums1[j]);
Arrays.sort(idx2, (i, j) -> nums2[i] - nums2[j]);
int[] ans = new int[n];
int left = 0, right = n - 1;
for (int i = 0; i < n; ++i) {
if (nums1[idx1[i]] > nums2[idx2[left]]) {
// 如果 nums1 当前最小值能战胜 nums2 的最小值,则直接匹配
ans[idx2[left]] = nums1[idx1[i]];
++left; // 移动 nums2 的指针
} else {
// 否则,用 nums1 的最小值去"消耗" nums2 的最大值
ans[idx2[right]] = nums1[idx1[i]];
--right; // 移动 nums2 的指针
}
}
return ans;
}
}
Python
python
class Solution:
def advantageCount(self, nums1: List[int], nums2: List[int]) -> List[int]:
n = len(nums1)
idx1 = sorted(range(n), key=lambda i: nums1[i]) # 根据 nums1 的值排序索引
idx2 = sorted(range(n), key=lambda i: nums2[i]) # 根据 nums2 的值排序索引
ans = [0] * n
left, right = 0, n - 1
for i in range(n):
if nums1[idx1[i]] > nums2[idx2[left]]:
# 如果 nums1 当前最小值能战胜 nums2 的最小值,则直接匹配
ans[idx2[left]] = nums1[idx1[i]]
left += 1 # 移动 nums2 的指针
else:
# 否则,用 nums1 的最小值去"消耗" nums2 的最大值
ans[idx2[right]] = nums1[idx1[i]]
right -= 1 # 移动 nums2 的指针
return ans
4、复杂度分析
- 时间复杂度 :
O(n log n)
,排序的时间复杂度。 - 空间复杂度 :
O(n)
,需要额外的数组存储索引和结果。
Q4、最低加油次数
1、题目描述
汽车从起点出发驶向目的地,该目的地位于出发位置东面 target
英里处。
沿途有加油站,用数组 stations
表示。其中 stations[i] = [positioni, fueli]
表示第 i
个加油站位于出发位置东面 positioni
英里处,并且有 fueli
升汽油。
假设汽车油箱的容量是无限的,其中最初有 startFuel
升燃料。它每行驶 1 英里就会用掉 1 升汽油。当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中。
为了到达目的地,汽车所必要的最低加油次数是多少?如果无法到达目的地,则返回 -1
。
注意:如果汽车到达加油站时剩余燃料为 0
,它仍然可以在那里加油。如果汽车到达目的地时剩余燃料为 0
,仍然认为它已经到达目的地。
示例 1:
输入:target = 1, startFuel = 1, stations = [] 输出:0 解释:可以在不加油的情况下到达目的地。
示例 2:
输入:target = 100, startFuel = 1, stations = [[10,100]] 输出:-1 解释:无法抵达目的地,甚至无法到达第一个加油站。
示例 3:
输入:target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]] 输出:2 解释: 出发时有 10 升燃料。 开车来到距起点 10 英里处的加油站,消耗 10 升燃料。将汽油从 0 升加到 60 升。 然后,从 10 英里处的加油站开到 60 英里处的加油站(消耗 50 升燃料), 并将汽油从 10 升加到 50 升。然后开车抵达目的地。 沿途在两个加油站停靠,所以返回 2 。
提示:
1 <= target, startFuel <= 109
0 <= stations.length <= 500
1 <= positioni < positioni+1 < target
1 <= fueli < 109
2、解题思路
方法一:动态规划
- 定义状态 :
dp[i]
表示加i
次油能到达的最远距离。 - 初始化 :
dp[0] = startFuel
,表示不加任何油时能到达的最远距离。 - 状态转移 :对于每个加油站,如果能到达该加油站,则尝试加油并更新
dp
数组。 - 结果检查 :遍历
dp
数组,找到最小的i
使得dp[i] >= target
。
方法二:贪心 + 优先队列
- 遍历加油站:模拟汽车行驶过程,记录当前燃料和已行驶距离。
- 燃料不足时加油:当燃料不足以到达下一个加油站时,从之前经过的加油站中选择油量最大的加油。
- 优先队列:使用优先队列存储可加油的加油站油量,每次取最大的进行加油。
3、代码实现
C++
c++
// 方法1: 动态规划
class Solution {
public:
int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {
int n = stations.size(); // 加油站的数量
std::vector<long> dp(n + 1, 0); // dp[i] 表示加 i 次油能行驶的最远距离
dp[0] = startFuel; // 初始状态:不加任何油时能行驶的距离
// 遍历每个加油站
for (int i = 0; i < n; ++i) {
// 从后往前更新 dp 数组,避免重复计算
for (int j = i; j >= 0; --j) {
if (dp[j] >= stations[i][0]) {
// 尝试加油, 更新 dp[j+1]
dp[j + 1] = max(dp[j + 1], dp[j] + stations[i][1]);
}
}
}
// 检查 dp 数组, 找到最小的加油次数
for (int i = 0; i <= n; ++i) {
if (dp[i] >= target) {
return i;
}
}
// 无法到达目的地
return -1;
}
};
c++
// 方法二: 贪心 + 优先队列 (堆)
class Solution {
public:
int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {
priority_queue<int> pq; // 最大堆, 存储可加油的油量
int ans = 0; // 记录加油次数
int prev = 0; // 上一个加油站的位置
int fuel = startFuel; // 当前剩余的燃料
int n = stations.size(); // 加油站的数量
// 遍历加油站, 包括目的地
for (int i = 0; i <= n; ++i) {
// 当前位置: 如果是最后一个加油站, 则为目的地
int curr = (i < n) ? stations[i][0] : target;
// 消耗燃料行驶到当前位置
fuel -= curr - prev;
// 如果燃料不足, 从之前经过的加油站中加油
while (fuel < 0 && !pq.empty()) {
fuel += pq.top(); // 加最多的油
pq.pop(); // 移除已加油的加油站
ans++; // 增加加油次数
}
// 如果仍然无法到达当前位置, 返回 -1
if (fuel < 0) {
return -1;
}
// 如果不是目的地, 将当前加油站的油量加入堆
if (i < n) {
pq.push(stations[i][1]);
prev = curr; // 更新上一个加油站的位置
}
}
return ans;
}
};
Java
java
// 方法1: 动态规划
class Solution {
public int minRefuelStops(int target, int startFuel, int[][] stations) {
int n = stations.length;
long[] dp = new long[n + 1];
dp[0] = startFuel;
for (int i = 0; i < n; ++i) {
for (int j = i; j >= 0; --j) {
if (dp[j] >= stations[i][0]) {
dp[j + 1] = Math.max(dp[j + 1], dp[j] + stations[i][1]);
}
}
}
for (int i = 0; i <= n; ++i) {
if (dp[i] >= target) {
return i;
}
}
return -1;
}
}
java
// 方法二: 贪心 + 优先队列 (堆)
class Solution {
public int minRefuelStops(int target, int startFuel, int[][] stations) {
PriorityQueue<Integer> pq = new PriorityQueue<>(Collections.reverseOrder());
int ans = 0, prev = 0, fuel = startFuel;
int n = stations.length;
for (int i = 0; i <= n; ++i) {
int curr = (i < n) ? stations[i][0] : target;
fuel -= curr - prev;
while (fuel < 0 && !pq.isEmpty()) {
fuel += pq.poll();
++ans;
}
if (fuel < 0) {
return -1;
}
if (i < n) {
pq.offer(stations[i][1]);
prev = curr;
}
}
return ans;
}
}
Python
python
# 方法1: 动态规划
class Solution:
def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int:
n = len(stations)
dp = [0] * (n + 1)
dp[0] = startFuel
for i in range(n):
for j in range(i, -1, -1):
if dp[j] >= stations[i][0]:
dp[j + 1] = max(dp[j + 1], dp[j] + stations[i][1])
for i in range(n + 1):
if dp[i] >= target:
return i
return -1
# 方法二: 贪心 + 优先队列 (堆)
class Solution:
def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int:
heap = []
ans, prev, fuel = 0, 0, startFuel
n = len(stations)
for i in range(n + 1):
curr = stations[i][0] if i < n else target
fuel -= curr - prev
while fuel < 0 and heap:
fuel += -heapq.heappop(heap)
ans += 1
if fuel < 0:
return -1
if i < n:
heapq.heappush(heap, -stations[i][1])
prev = curr
return ans
4、复杂度分析
- 方法一 :
- 时间复杂度 :
O(n^2)
,其中n
是加油站数量。 - 空间复杂度 :
O(n)
,存储dp
数组。
- 时间复杂度 :
- 方法二 :
- 时间复杂度 :
O(n log n)
,优先队列操作的时间复杂度。 - 空间复杂度 :
O(n)
,优先队列的空间。
- 时间复杂度 :