第 93 场周赛:二进制间距、重新排序得到 2 的幂、优势洗牌、最低加油次数

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、解题思路
  1. 遍历二进制位 :从最低位到最高位逐位检查 n 的二进制表示。
  2. 记录 1 的位置 :用一个变量 last 记录上一个 1 的位置。
  3. 计算距离 :每遇到一个 1,计算当前 1 和上一个 1 的距离,并更新最大值。
  4. 返回结果:最后返回最大的距离。
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、解题思路

方法一:回溯法

  1. 生成所有排列 :将 n 的数字重新排列,检查是否有排列是 2 的幂。
  2. 避免重复计算:排序数字并跳过重复数字的递归分支。
  3. 检查 2 的幂 :使用 n & (n - 1) == 0 判断是否为 2 的幂。
  4. 优化:剪枝(跳过前导零和重复数字)。

方法二:数字频率统计

  1. 预计算所有 2 的幂的数字频率:生成所有 2 的幂(1 到 1e9),统计每个数字的频率。
  2. 比较频率 :统计 n 的数字频率,检查是否与某个 2 的幂的频率匹配。
  3. 优化:使用哈希表存储所有 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!)mn 的位数(最坏情况)。
    • 空间复杂度O(m),递归栈和标记数组。
  • 方法二
    • 时间复杂度O(1),预计算后查询是常数时间。
    • 空间复杂度O(1),预计算存储所有 2 的幂的数字频率。

Q3、优势洗牌

1、题目描述

给定两个长度相等的数组 nums1nums2nums1 相对于 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、解题思路
  1. 排序并保留原始索引

    • nums1nums2 进行排序,但需要保留原始索引以便最后放置结果。
  2. 贪心匹配

    • 使用双指针,尝试用 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、解题思路

方法一:动态规划

  1. 定义状态dp[i] 表示加 i 次油能到达的最远距离。
  2. 初始化dp[0] = startFuel,表示不加任何油时能到达的最远距离。
  3. 状态转移 :对于每个加油站,如果能到达该加油站,则尝试加油并更新 dp 数组。
  4. 结果检查 :遍历 dp 数组,找到最小的 i 使得 dp[i] >= target

方法二:贪心 + 优先队列

  1. 遍历加油站:模拟汽车行驶过程,记录当前燃料和已行驶距离。
  2. 燃料不足时加油:当燃料不足以到达下一个加油站时,从之前经过的加油站中选择油量最大的加油。
  3. 优先队列:使用优先队列存储可加油的加油站油量,每次取最大的进行加油。
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),优先队列的空间。