Python技术迷

今年的就业环境,太差。。。

用一句话证明今年的就业环境有多差?这是我在网上看到一网友的发帖,目前已经冲上某脉热搜第一

今年的就业环境大家都是心知肚明的,只是能有多差,我还是想看看网友们怎么秀。

Image

一位网友开门见山:“boss直聘在ios下载排行榜上排名第三,这还用问吗?” 有点东西,但不够秀。

Image

另一个网友也不甘示弱:“我把 boss 当抖音刷” 好家伙,高端玩家。

Image

更有位网友张口就来:“以前我吃啥狗吃啥,现在狗吃啥我吃啥!” 整活还得是你。

Image

而大部分网友的回答,都表达了一个意思:“未读、送达、已读不回。” 短短几个字,却砍出了真伤。

Image

看到网友们还能开玩笑,如此乐观的态度,突然就感觉轻松很多了。虽然环境就这样,但是我们还得向前看,加油,你我共勉。

下面是今日的大厂算法题
今日算法题,来自LeetCode的第40题:组合总和 II,下面是我的算法思路及实现,让我们来看看吧。
算法题目

给定一个可能包含重复元素的整数数组 candidates 和一个目标数 target,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

  • 所有数字(包括目标数)都是正整数。

  • 解集不能包含重复的组合。

专属福利 
👉点击领取:最全Python资料合集
算法思路

由于不使用回溯算法,我们可以考虑动态规划(Dynamic Programming,DP)作为解决方案。具体的动态规划方法可能在处理重复元素和保证每个数字只使用一次上有所不同,这里介绍一种处理方式:

  1. 排序:首先对 candidates 数组进行排序,以方便处理重复元素。

  2. 初始化DP表:创建一个列表(或数组)dp,其中 dp[i] 存储所有和为 i 的组合列表。dp[0] 初始化为一个空列表的列表,表示和为0的组合仅有空集。

  3. 填充DP表:遍历每个数字,对于每个数字,反向遍历从 target 到该数字的值,更新 dp[i] 的值。这样做可以确保每个数字仅被使用一次。

  4. 避免重复组合:在更新 dp[i] 时,如果当前数字与前一个数字相同,则只在上一轮新增的组合基础上添加当前数字,避免产生重复的组合。

代码实现
由于动态规划的实现比较复杂且该问题不太适合完全不使用递归或回溯的方式解决,下面提供一个基于回溯思想但尽量减少递归深度的解法,作为对问题理解的补充。注意,这并不是纯动态规划的解法,而是一种折中的尝试。
Java实现

Java 实现同样依赖于先对数组进行排序,然后使用递归和剪枝的方式来避免重复组合。

import java.util.ArrayList;import java.util.Arrays;import java.util.List;
public class Solution { public List<List<Integer>> combinationSum2(int[] candidates, int target) { List<List<Integer>> result = new ArrayList<>(); Arrays.sort(candidates); // 排序 backtrack(result, new ArrayList<>(), candidates, target, 0); return result; }
private void backtrack(List<List<Integer>> result, List<Integer> tempList, int[] candidates, int remain, int start) { if (remain < 0) return; // 剪枝 else if (remain == 0) result.add(new ArrayList<>(tempList)); // 找到一个组合 else { for (int i = start; i < candidates.length; i++) { if (i > start && candidates[i] == candidates[i-1]) continue; // 跳过重复元素 tempList.add(candidates[i]); backtrack(result, tempList, candidates, remain - candidates[i], i + 1); // 递归 tempList.remove(tempList.size() - 1); // 回溯 } } }}
JavaScript实现(折中方案,非纯DP)
function combinationSum2(candidates, target) {    let result = [];    candidates.sort((a, b) => a - b); // 排序以方便处理重复元素
const findCombination = (start, target, path) => { if (target === 0) { result.push([...path]); return; } for (let i = start; i < candidates.length; i++) { if (i > start && candidates[i] === candidates[i-1]) continue; // 跳过重复元素 if (candidates[i] > target) break; // 由于数组已排序,后续不会有合法组合 findCombination(i + 1, target - candidates[i], [...path, candidates[i]]); } };
findCombination(0, target, []); return result;}

Go实现

Go 的实现也需要先排序,然后利用递归来遍历所有可能的组合,同时注意避免重复。

package main
import ( "sort")
func combinationSum2(candidates []int, target int) [][]int { sort.Ints(candidates) // 排序 result := [][]int{} backtrack(&result, []int{}, candidates, target, 0) return result}
func backtrack(result *[][]int, temp []int, candidates []int, remain int, start int) { if remain < 0 { return // 剪枝 } else if remain == 0 { comb := make([]int, len(temp)) copy(comb, temp) *result = append(*result, comb) // 找到一个组合 } else { for i := start; i < len(candidates); i++ { if i > start && candidates[i] == candidates[i-1] { continue // 跳过重复元素 } // 递归并回溯 backtrack(result, append(temp, candidates[i]), candidates, remain-candidates[i], i+1) } }}

算法解析

这个折中方案使用了排序和递归,但通过跳过重复元素和合理的剪枝来减少不必要的递归调用。虽然不是纯动态规划解法,但也有效避免了生成重复的组合,并尽量减少了递归深度。

示例和测试

输入:candidates = [10,1,2,7,6,1,5], target = 8输出:[[1,1,6],[1,2,5],[1,7],[2,6]]输入:candidates = [2,5,2,1,2], target = 5输出:[[1,2,2],[5]]
通过上述代码,我们可以找到所有可能的组合总和II解,且不会包含重复的组合。
总结
组合总和II问题引入了元素只能使用一次及输入数组可能包含重复元素的限制,使得问题的解决更为复杂。本文介绍的解决方案虽然依赖于排序和递归,但尽量减少了递归的使用,并通过特定的策略避免了重复组合的生成。
Image
 1
Image
热门推荐

Image