每天一道LeetCode-----找到给定序列中所有和为某个值的集合或集合个数,序列中可以有/无重复项,集合元素顺序不同算不同集合等
Combination Sum
原題鏈接Combination Sum
給定一個無重復項的序列,找到所有和是target的集合,每個元素可以使用多次。
可以用深度優先(dfs),對于某個元素都有兩種選擇,一種是選擇當前元素至少一次,一種是不選擇當前元素,所以在查找集合時要分開處理。比如說,當前元素下標為1,那么
這兩種情況都可以用一個循環來做,從當前位置開始遍歷給定序列,對于每個元素,如果選擇了那么就是情況1,如果沒有選擇就是情況2
代碼如下
class Solution { public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {/* 按遞增排序,遞歸時可以根據當前要選擇的元素和target大小決定是否還需要繼續 */std::sort(candidates.begin(), candidates.end());/* 結果 */vector<vector<int>> res;/* 當前找到的集合 */vector<int> cur;dfs(candidates, target, 0, cur, res);return res;} private:/* 深度優先查找所有滿足條件的集合,target代表和要求總和的差,為0表示找到一個集合 */void dfs(vector<int>& candidates, int target, int idx, vector<int>& cur, vector<vector<int>>& res){/* 找到一個集合,添加到結果集中 */if(target == 0){res.emplace_back(cur);}/* 如果當前位置越界或者當前元素大于所要求的數值,返回,因為是遞增排序,后面的元素肯定也大于 */if(idx >= candidates.size() || candidates[idx] > target)return;for(int i = idx; i < candidates.size(); ++i){cur.emplace_back(candidates[i]);dfs(candidates, target - candidates[i], i, cur, res);cur.pop_back();}} };當然,如果覺得最后一個for循環不容易理解,可以把for循環去掉,改成
/* 情況1 */ cur.emplace_back(candidates[i]); dfs(candidates, target - candidates[i], i, cur, res); /* 情況2 */ cur.pop_back(); dfs(candidates, target, i + 1, cur, res);不過這樣會增加遞歸次數,但結果也對。
Combination Sum II
原題鏈接Combination Sum II
上面的擴展,序列中可以有重復元素,求所有和為target的集合。
這里主要是解決集合重復問題,比如示例中的序列有兩個1,那么第一個1和2,5構成的集合滿足條件,第2個1和2,5構成的集合也滿足條件,但是[1,2,5]這個集合不可以重復出現,所以結果中只有一個[1,2,5]。
思路還是和上面一樣,深度優先(dfs),但是需要解決重復問題,一種方法是用一個std::set<vector<int>>存儲所有已找到的集合,在添加新集合之前判斷一下是否在set中出現過,但是這種方法仍然需要把所有重復的集合都遍歷出來,比較慢,有什么方法可以在搖籃里扼殺掉重復集合的出現呢。
因為在每層遞歸中都是在for循環中選擇某一個,然后開啟下層循環,所以可以嘗試在for循環中判斷當前選擇的這個元素有沒有遞歸下去的必要。
打個比方,為了直觀,以[10,1,2,7,6,5,5,5]序列為例,排序后為[1,2,5,5,5,6,7,10]。這里把示例中的一個1換成兩個5,為了讓重復的那項在中間,更有一般性。
所以無論是在所有的5中選擇一個,還是選擇某些個5,都只需要考慮重復項的第一個元素。
代碼如下
Combination Sum III
原題鏈接Combination Sum III
和前面的要求不太一樣,本題要求最后的集合的個數為k,每個元素都在1-9這個范圍,同時每個元素不能在一個集合中出現多次。
這個就直接用上面的方法找就可以了
Combination Sum IV
原題鏈接Combination Sum IV
一個結果集合中同一個數字可以出現多次,順序不同的集合算作不同的結果,返回有多少種可能的集合。
因為每次遞歸都可以選擇給定序列的每一個元素,所以如果每次遞歸都遍歷一遍給定序列的話,唔…超時了…為什么會超時,當然是過多的重復計算了,對于某個剩余大小n,不同的遍歷過程可能是相同的。比如說某次遞歸要計算和為n的序列有多少個,下次又有另一個遞歸也要計算和為n的序列有多少個,這就造成了重復計算
怎么解決呢,當然動態規劃了,解決重復計算是動態規劃的設計初衷啊,這里沒要求把所有集合都找到,只是找個數,用動態規劃自然比較輕松。
代碼如下
class Solution { public:int combinationSum4(vector<int>& nums, int target) {std::sort(nums.begin(), nums.end());vector<int> dp(target + 1, -1);return dfs(nums, target, dp); } private:int dfs(vector<int>& nums, int target, vector<int>& dp){/* * 如果計算過和為target的集合個數,直接返回 * dp[target]代表和為target的集合序列有多少個*/if(dp[target] != -1)return dp[target];int n = 0;if(target == 0){n = 1;}else{for(int i = 0; i < nums.size(); ++i){if(nums[i] > target)break;n += dfs(nums, target- nums[i], dp);}}dp[target] = n;return n;} };上面四道題都是形如給定一個序列,找到和為某個數的集合,主要使用的方法是深度優先遍歷。難點在處理特殊情況上,比如解決重復集合問題,只考慮重復元素的第一個。再比如動態規劃的使用,可以有效避免重復計算,提高效率。
總結
以上是生活随笔為你收集整理的每天一道LeetCode-----找到给定序列中所有和为某个值的集合或集合个数,序列中可以有/无重复项,集合元素顺序不同算不同集合等的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 每天一道LeetCode-----将字符
- 下一篇: 每天一道LeetCode-----找到序