1. 前言
由於後面還有很多題型要寫,貪心演算法目前可能就到此為止了,上一篇部落格的地址為
LeetCode解題記錄(貪心演算法)(一)
下面正式開始我們的刷題之旅
2. 貪心
763. 劃分字母區間(中等)
思路
想切割,要有首尾兩個指標,確定了結尾指標,就能確定下一個切割的開始指標。
遍歷字串,如果已掃描部分的所有字元,都只出現在已掃描的範圍內,即可做切割。
注意 : 貪心的思想為,只要是掃描過的字元,都出現在我掃描的範圍之類,我就切割,不去考慮其他的條件,這樣能保證切割的數量最多
程式碼實現思路
- result 用來儲存結果
- start end,片斷的首尾指標
- map 存放每一個字元的最遠位置
首先通過一個map,記錄了每個字元的最遠的位置,接下來遍歷字串,end是用來記錄,當前已經掃描的字串的最遠的出現的位置,如果當前的位置,等於掃描的字串的最遠位置,則,可以證明,到達了切割的條件,然後切割,存到result裡
class Solution {
public:
vector<int> partitionLabels(string S) {
vector<int> result;
unordered_map<char, int> map; //記錄char c 和其最後出現位置的 map
int start = 0, end = 0;
// 初始化map,記錄每一個字元的最後位置
for (int i = 0; i < S.size(); i ++) {
map[S[i]] = i;
}
for (int i = 0; i < S.size(); i ++) {
end = max(end, map[S[i]]);//記錄已掃描字元的最後一次出現的位置
if (i == end) {//說明後面的片段沒有出現重複的字母了
result.push_back(end - start + 1);//記錄結果
start = i + 1;
}
}
return result;
}
};
406. 根據身高重建佇列(中等)
解題思路
貪心演算法:按照身高從高到低進行排序,矮的放後面,因為矮的即使放在了高的前面,也不會對之前高的產生影響;但高的放在前面,對矮的結果就會產生影響了。
重寫 compare() 方法:身高相同,按照個數升序排序;身高不同,按照身高降序排序。
public int compare(int[] o1, int[] o2) {
// 先按身高降序,若身高相同則按 k 值升序。
return o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0];
}
第二個數字作為索引位置,把陣列放在目標索引位置上,如果原來有數了,會往後移。(在一個 ListList 中的指定位置插入一個元素,當前指定位置的元素會往後面移動一個位置。)
遍歷排序後的陣列,根據 K 插入到 K 的位置上。
class Solution {
public int[][] reconstructQueue(int[][] people) {
int n = people.length;
int m = people[0].length;
if (n == 0 || m == 0) return new int[0][0];
Arrays.sort(people, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
// 先按身高降序,若身高相同則按 k 值升序。
return o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0];
}
});
// 遍歷排序後的陣列,根據 K 插入到 K 的位置上。
List<int[]> list = new ArrayList<>();
for (int[] i : people) {
list.add(i[1], i);
}
return list.toArray(new int[list.size()][2]);
}
}
665. 非遞減數列
給你一個長度為 n 的整數陣列,請你判斷在 最多 改變 1 個元素的情況下,該陣列能否變成一個非遞減數列。
我們是這樣定義一個非遞減數列的: 對於陣列中任意的 i (0 <= i <= n-2),總滿足 nums[i] <= nums[i + 1]。
示例 1:
輸入: nums = [4,2,3]
輸出: true
解釋: 你可以通過把第一個4變成1來使得它成為一個非遞減數列。
示例 2:
輸入: nums = [4,2,1]
輸出: false
解釋: 你不能在只改變一個元素的情況下將其變為非遞減數列。
提示:
1 <= n <= 10 ^ 4
10 ^ 5 <= nums[i] <= 10 ^ 5
題解:
貪心演算法
本題是要維持一個非遞減的數列,所以遇到遞減的情況時(nums[i] > nums[i + 1]),要麼將前面的元素縮小,要麼將後面的元素放大。
但是本題唯一的易錯點就在這,
如果將nums[i]縮小,可能會導致其無法融入前面已經遍歷過的非遞減子數列;
如果將nums[i + 1]放大,可能會導致其後續的繼續出現遞減;
所以要採取貪心的策略,在遍歷時,每次需要看連續的三個元素,也就是瞻前顧後,遵循以下兩個原則:
需要儘可能不放大nums[i + 1],這樣會讓後續非遞減更困難;
就是能不放大就不放大,儘量與前面持平
如果縮小nums[i],但不破壞前面的子序列的非遞減性;
演算法步驟:
遍歷陣列,如果遇到遞減:
還能修改:
修改方案1:將nums[i]縮小至nums[i + 1];
這個方案是,用i-1,i,i+1,來表示三個數的位置,其中i是我們發現大於i+1的數,那麼當i+1大於i-1的時候,我們應該將i縮小至i+1,為什麼呢,你想啊,你右邊的數比你小,你左邊的數比你小,但是呢,你右邊的數比你左邊的數高,你是不是隻需要和你右邊的一樣高,就能保持非遞減?如果你不這樣,你讓右邊的數增加,這就違反了上面的第一條原則:需要儘可能不放大nums[i + 1],這樣會讓後續非遞減更困難,因為這種情況,我們的選擇是縮小i
修改方案2:將nums[i + 1]放大至nums[i];
這種情況是什麼呢,與上一種相反,你左邊右邊的數都比你小,但是你右邊的數比你左邊的數要小,這個時候我們就應該將右邊的數放大,放大至nums[i],也就是你的大小,這種情況下是沒有爭論的,只能將右邊的數放大,不明白的同學可以自己在紙上畫一畫
如果不能修改了:直接返回false;
這個程式碼需要修改兩個地方,顯然不符合題目要求,返回false
程式碼如下
flag 代表修改機會,因為只有一次,所以用掉了,flag就變成false
class Solution {
public:
bool checkPossibility(vector<int>& nums)
{
if (nums.size() == 1) return true;
bool flag = nums[0] <= nums[1] ? true : false; // 標識是否還能修改
// 遍歷時,每次需要看連續的三個元素
for (int i = 1; i < nums.size() - 1; i++)
{
if (nums[i] > nums[i + 1]) // 出現遞減
{
if (flag) // 如果還有修改機會,進行修改
{
if (nums[i + 1] >= nums[ i - 1])// 修改方案1
nums[i] = nums[i + 1];
else // 修改方案2
nums[i + 1] = nums[i];
flag = false; // 用掉唯一的修改機會
}
else // 沒有修改機會,直接結束
return false;
}
}
return true;
}
};
執行結果
3. 總結
對不起各位,我演算法這塊更新的實在是太慢了,不過最近真的很忙很忙,當然有時候我也會娛樂一下,沒有做到自律,下個星期我還準備開始寫計算機網路和作業系統的專欄,所以演算法這塊,我儘量多寫(其實我就是懶,有時候晚上回家真的好累,不想寫演算法題,,,,)
總之,下個星期,繼續努力,與昨天的自己比較!