1. 分配餅乾
Input: grid[1,3], size[1,2,4]
Output: 2
題目描述:每個孩子都有一個滿足度 grid,每個餅乾都有一個大小 size,只有餅乾的大小大於等於一個孩子的滿足度,該孩子纔會獲得滿足。求解最多可以獲得滿足的孩子數量。
- 給一個孩子的餅乾應當儘量小並且又能滿足該孩子,這樣大餅乾才能拿來給滿足度比較大的孩子。
- 因爲滿足度最小的孩子最容易得到滿足,所以先滿足滿足度最小的孩子。
解題思想:在以上的解法中,我們只在每次分配時餅乾時選擇一種看起來是當前最優的分配方法,但無法保證這種局部最優的分配方法最後能得到全局最優解。我們假設能得到全局最優解,並使用反證法進行證明,即假設存在一種比我們使用的貪心策略更優的最優策略。如果不存在這種最優策略,表示貪心策略就是最優策略,得到的解也就是全局最優解。
JAVA代碼:
class Solution {
//貪心問題
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);//先對孩子的胃口值進行排序
Arrays.sort(s);//再對餅乾值進行排序
int gi=0;
int si=0;
while(gi<=g.length-1&&si<=s.length-1){
if(g[gi]<=s[si]){
gi++;
}
si++;
}
return gi;
}
}
2.不重疊的區間個數
435.無重疊區間 (452類似)
給定一個區間的集合,找到需要移除區間的最小數量,使剩餘區間互不重疊。
注意:
可以認爲區間的終點總是大於它的起點。區間 [1,2] 和 [2,3] 的邊界相互“接觸”,但沒有相互重疊。
示例 1:
輸入: [ [1,2], [2,3], [3,4], [1,3] ]
輸出: 1
解釋: 移除 [1,3] 後,剩下的區間沒有重疊。
示例 2:
輸入: [ [1,2], [1,2], [1,2] ]
輸出: 2
解釋: 你需要移除兩個 [1,2] 來使剩下的區間沒有重疊。
示例 3:
輸入: [ [1,2], [2,3] ]
輸出: 0
解釋: 你不需要移除任何區間,因爲它們已經是無重疊的了。
解題思想:貪心策略:統計不重合的最大區間集合總數,所有任務的數量-不重合的最大區間集合總數=刪除的重疊區間數。 按照每個區間的end排序(結束時間最早,比如你一天要參加幾個活動,這個活動開始的多早其實不重要,重要的是你結束的多早,早晨7點就開始瞭然後一搞搞一天,那你今天也就只能參加這一個活動;但如果這個活動開始的不早,比如9點纔開始,但是隨便搞搞10點就結束了,那你接下來就還有大半天的時間可以參加其他活動),將第一個區間的end設置爲默認end,依次遍歷排序後的其他區間,進行重疊性判斷。
JAVA代碼:
import java.util.Arrays;
import java.util.Comparator;
/**
* Created by 高先森 on 2020/6/20.
*/
public class leetcode_435_Non_overlappingIntervals {
public static void main(String[] args){
int[][] ints={{1,2},{2,3},{3,4},{1,3}};
//解題思想:貪心策略
// 統計不重合的最大區間集合總數,所有任務的數量-不重合的最大區間集合總數=刪除的重疊區間數。
// 按照每個區間的end排序,將第一個區間的end設置爲默認end,依次遍歷排序後的其他區間,進行重疊性判斷
System.out.println(eraseOverlapIntervals(ints));
}
public static int eraseOverlapIntervals(int[][] intervals) {
if(intervals.length==0)
return 0;
//1.二維數組N*2存放的每個任務的起止時間star和end,先按照end對二位數組進行從小到大排序
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[1]-o2[1];//從小到大排序
}
});
//2.選取第一個區間的end爲默認end,依次遍歷intervals其它的區間,進行重疊性判斷
int temp_end=intervals[0][1]; ////第一個任務end最小,所以第一個選出第一個任務的end
int start,end,count=1;
for(int[] inte:intervals){
start=inte[0];
end=inte[1];
//如果當前區間的start在已經選個的區間中最後一個end之前,則當前區間重疊,忽略處理
if(start<temp_end)//相等不屬於重合
continue;
//否則:當前區間不重疊,作爲可選任務區間,總數加1,所有區間的end發生改變
count++;
temp_end=end;
}
//3.返回要刪除的區間數目
return intervals.length-count;
}
}
3. 射箭刺破氣球
在二維空間中有許多球形的氣球。對於每個氣球,提供的輸入是水平方向上,氣球直徑的開始和結束座標。由於它是水平的,所以y座標並不重要,因此只要知道開始和結束的x座標就足夠了。開始座標總是小於結束座標。平面內最多存在10的4次個氣球。一支弓箭可以沿着x軸從不同點完全垂直地射出。在座標x處射出一支箭,若有一個氣球的直徑的開始和結束座標爲 xstart,xend, 且滿足 xstart ≤ x ≤ xend,則該氣球會被引爆。可以射出的弓箭的數量沒有限制。 弓箭一旦被射出之後,可以無限地前進。我們想找到使得所有氣球全部被引爆,所需的弓箭的最小數量。
Example:
輸入:
[[10,16], [2,8], [1,6], [7,12]]
輸出:
2
解釋:
對於該樣例,我們可以在x = 6(射爆[2,8],[1,6]兩個氣球)和 x = 11(射爆另外兩個氣球)。
解題思想:使用貪心策略,因爲一支箭可以射穿重疊區間內的氣球,每個不重疊區間內的氣球都額外需要一直箭射穿,將此問題轉換爲不重合的最大區間集合總數(不重疊區間的數目也就是所需要的最少弓箭數目),算法如下:
算法:
根據 x_end 將氣球進行排序。
初始化 first_end 爲第一個氣球結束的座標 points[0][1]。
初始化箭的數量 arrows = 1。
遍歷所有的氣球:
如果氣球的開始座標小於等於first_end:(邊界可以算重合,能射穿)
跳過當前氣球區間(重疊:同一支箭可以射穿)
否則
增加箭的數量(需要額外一支弓箭射穿當前氣球)。
將 first_end 設置爲當前氣球的 x_end。
返回 arrows。
JAVA代碼如下;
public static int findMinArrowShots(int[][] points) {
//貪心策略:
//1.先按照氣球的end進行排序
Arrays.sort(points, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[1]-o2[1];//從小到大排序
}
});
//2.遍歷每個氣球區間,進行重疊性判斷(用最少數量的箭引爆氣球)
// 算法:
// 根據 x_end 將氣球進行排序。
// 初始化 first_end 爲第一個氣球結束的座標 points[0][1]。
// 初始化箭的數量 arrows = 1。
// 遍歷所有的氣球:
// 如果氣球的開始座標大於 first_end:
// 則增加箭的數量。
// 將 first_end 設置爲當前氣球的 x_end。
// 否則跳過當前氣球區間(重疊:同一支箭可以射穿)
// 返回 arrows。
int temp_end=points[0][1],num=1;//對temp_end進行初始話賦值,一開始弓箭的數目爲1
int start,end;
for(int[] p:points) {
start = p[0];
end = p[1];
if (start <= temp_end) {//如果當前氣球的start區間小於temp_end,說明可以用同一支弓箭射穿,此處邊界處相等也算射穿
continue;
}
num++;//否則還得額外增加一支弓箭才能射穿當前氣球
temp_end = end;
}
return num;
}
4. 根據身高和序號重組隊列
假設有打亂順序的一羣人站成一個隊列。 每個人由一個整數對(h, k)表示,其中h是這個人的身高,k是排在這個人前面且身高大於或等於h的人數。 編寫一個算法來重建這個隊列。
注意:
總人數少於1100人。
示例
輸入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
輸出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
解題思路:
解題思路:先排序再插入
1.排序規則:按照身高從大到小排序,身高相同的按照K值從小到大排序(因爲身高相同時候,k的值依次遞增才符合題意)
2.遍歷排序後的數組,根據K插入到K的位置上
核心思想:高個子先站好位,矮個子插入到K位置上,對於當前矮個子而言:前面肯定有K個高個子以及站好位,
對於當前高個子而言:矮個子的插入對高個子本身的k不受影響
JAVA代碼如下:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* Created by 高先森 on 2020/6/21.
*/
public class leetcode_406_QueueReconstructionbyHeight {
public static void main(String[] args){
int[][] ints={{7,0},{4,4}, {7,1}, {5,0}, {6,1}, {5,2}};
//[9,0],[7,0],[1,9],[3,0],[2,7],[5,3],[6,0],[3,4],[6,2],[5,2]
int[][] ints1={{9,0},{7,0},{1,9},{3,0},{2,7},{5,3},{6,0},{3,4},{6,2},{5,2}};
reconstructQueue(ints1);
}
// 解題思路:先排序再插入
// 1.排序規則:按照身高從大到小排序,身高相同的按照K值從小到大排序(因爲身高相同時候,k的值一次遞增才符合題意)
// 2.遍歷排序後的數組,根據K插入到K的位置上
// 核心思想:高個子先站好位,矮個子插入到K位置上,對於當前矮個子而言:前面肯定有K個高個子,
// 對於高個子而言:矮個子的插入對高個子的k不受影響
//示例一:
//[9,0],[7,0],[1,9],[3,0],[2,7],[5,3],[6,0],[3,4],[6,2],[5,2]
//排序:[9,0],[7,0],[6,0],[6,2],[5,2],[5,3],[3,0],[3,4],[2,7],[1,9]
//一個一個插入到自己的k位置上
//[9,0]
//[7,0],[9,0]
//[6,0],[7,0],[9,0]
//[6,0],[7,0],[6,2],[9,0]
//[6,0],[7,0],[5,2],[6,2],[9,0]
//[6,0],[7,0],[5,2],[5,3],[6,2],[9,0]
//[3,0],[6,0],[7,0],[5,2],[5,3],[6,2],[9,0]
//[3,0],[6,0],[7,0],[5,2],[3,4],[5,3],[6,2],[9,0]
//[3,0],[6,0],[7,0],[5,2],[3,4],[5,3],[6,2],[2,7],[9,0]
//[3,0],[6,0],[7,0],[5,2],[3,4],[5,3],[6,2],[2,7],[9,0],[1,9]
//示例二:
// [7,0], [7,1], [6,1], [5,0], [5,2], [4,4]
// 再一個一個插入。
// [7,0]
// [7,0], [7,1]
// [7,0], [6,1], [7,1]
// [5,0], [7,0], [6,1], [7,1]
// [5,0], [7,0], [5,2], [6,1], [7,1]
// [5,0], [7,0], [5,2], [6,1], [4,4], [7,1]
public static int[][] reconstructQueue(int[][] people) {
//1.按照身高從大到小排序,身高相同的按照K值從小到大排序(因爲身高相同時候,k的值一次遞增才符合題意)
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]; //個人理解:return返回大於0時,o1和o2交換
}
});
//2.在K位置上插入當前(h,k)
List<int[]> list=new ArrayList<>();
for(int[] p:people)
list.add(p[1],p);//參數1爲index(當前人(h,k)的k),參數2爲添加到list中的人(h,k)
return list.toArray(new int[list.size()][2]);
}
}
5. 買賣股票最大的收益
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
如果你最多隻允許完成一筆交易(即買入和賣出一支股票一次),設計一個算法來計算你所能獲取的最大利潤。
注意:你不能在買入股票前賣出股票。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
注意利潤不能是 7-1 = 6, 因爲賣出價格需要大於買入價格;同時,你不能在買入前賣出股票。
示例 2:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。
解題思想:
用minPrice記錄最低的股票買入價格,初始話爲第一支股票的價格,依次遍歷剩餘的股票,用maxValue記錄當前股票價賣出獲得的最大價值。
JAVA代碼如下:
class Solution {
public int maxProfit(int[] prices) {
if(prices.length<=0)
return 0;
int minPrice=prices[0];
int maxValue=0;
for(int price:prices){
if(price<minPrice)
minPrice =price;
else
maxValue=maxValue>(price-minPrice)?maxValue:(price-minPrice);
}
return maxValue;
}
}
6. 買賣股票的最大收益 II
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。
示例 2:
輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。
因爲這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。
示例 3:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。
解題思想:
循環遍歷每支股票,買入時機:如果當前手裏不持有股票並且之後的股票價會長則買入(flag==0&&prices[i]<prices[i+1]),賣出時機:手裏持有股票並且當前股票價格大於持有股票的買入價格,並且後續發生降價(如果繼續增值prices[i]<prices[i+1],不會選擇賣出)。
JAVA代碼如下:
/**
* Created by 高先森 on 2020/6/21.
*/
public class leetcode_121_121_bestTimeToBuyAndSellStock {
public static void main(String[] args){
int[] ints={7,1,5,3,6,4};
int[] ints1={1,2,3,4,5};
System.out.println(maxProfit122(ints1));
}
public static int maxProfit122(int[] prices) {
if(prices.length<=0)
return 0;
int buyPrice=0;
int value=0;
int flag=0;//用於標記手裏是否持有股票(要求手裏同時只能持有一支股票)
int i;
for(i=0;i<prices.length-1;i++){
//1.如果當前手裏不持有股票並且之後的股票價會長則買入
if(flag==0&&prices[i]<prices[i+1]){
buyPrice=prices[i];
flag=1;//當前手裏持有股票
}else if(flag==1&&prices[i]>buyPrice&&prices[i]>prices[i+1]){
//2.賣出:手裏持有股票並且當前股票價格大於持有股票的買入價格,並且發生降價(如果繼續增值prices[i]<prices[i+1],不會選擇賣出)
value=value+prices[i]-buyPrice;//盈利
flag=0;
}
}
if(flag==1) //因爲i遍歷到最後一直股票會退出,如果手裏仍持有股票,則最後一直股票價格一定大於持有股票價格,賣出
value=value+(prices[i]-buyPrice);
return value;
}
}
7.種花問題
假設你有一個很長的花壇,一部分地塊種植了花,另一部分卻沒有。可是,花卉不能種植在相鄰的地塊上,它們會爭奪水源,兩者都會死去。給定一個花壇(表示爲一個數組包含0和1,其中0表示沒種植花,1表示種植了花),和一個數 n 。能否在不打破種植規則的情況下種入 n 朵花?能則返回True,不能則返回False。
示例 1:
輸入: flowerbed = [1,0,0,0,1], n = 1
輸出: True
示例 2:
輸入: flowerbed = [1,0,0,0,1], n = 2
輸出: False
注意:
數組內已種好的花不會違反種植規則。
輸入的數組長度範圍爲 [1, 20000]。
n 是非負整數,且不會超過輸入數組的大小
解題思想:使用left,right分別標記當前位置前面和後面的種花情況,如果都是0,則當前位置可以種。
JAVA代碼:
/**
* Created by 高先森 on 2020/6/24.
*/
public class leetcode_605_canPlaceFlowers {
public static void main(String[] args){
int[] ints={1,0,0,0,1};
System.out.println(canPlaceFlowers(ints,2));
}
public static boolean canPlaceFlowers(int[] flowerbed, int n) {
int maxnum=0,left,right;
int len=flowerbed.length;
for(int i=0;i<len;i++){
//1.已經種植的直接略過
if(flowerbed[i]==1)
continue;
//2.確定left值
left=(i==0?flowerbed[i]:flowerbed[i-1]);
//3.確定right值
right=(i==len-1?flowerbed[i]:flowerbed[i+1]);
//4.當前位置i可以種植
if(left==0&&right==0){
flowerbed[i]=1;
maxnum++;
}
}
return maxnum>=n?true:false;
}
}
8.判斷是否爲子序列
給定字符串 s 和 t ,判斷 s 是否爲 t 的子序列。
你可以認爲 s 和 t 中僅包含英文小寫字母。字符串 t 可能會很長(長度 ~= 500,000),而 s 是個短字符串(長度 <=100)。
字符串的一個子序列是原始字符串刪除一些(也可以不刪除)字符而不改變剩餘字符相對位置形成的新字符串。(例如,"ace"是"abcde"的一個子序列,而"aec"不是)。
示例 1:
s = "abc", t = "ahbgdc"
返回 true.
示例 2:
s = "axc", t = "ahbgdc"
返回 false.
解題思想:1.使用雙指針分別遍歷s和t進行字符判斷 2.遍歷短串s,利用t.indexof(參數1,參數2)進行字符匹配判斷
JAVA代碼:
/**
* Created by 高先森 on 2020/6/24.
*/
public class leetcode_392_isSubsequence {
public static void main(String[] args){
isSubsequence1("abE","ahbgdc");
}
public static boolean isSubsequence(String s, String t) {
int sLen=s.length();
int tLen=t.length();
int i=0,j=0;
while (i<sLen&&j<tLen){
if(s.charAt(i)==t.charAt(j)){
i++;
j++;
}else
j++;
}
return i==sLen?true:false;
}
public static boolean isSubsequence1(String s, String t) {
int index=-1;
for(char ch:s.toCharArray()){
//從index+1的位置開始匹配ch,返回匹配到的t中ch的下標
index=t.indexOf(ch,index+1);
if(index==-1)//如果沒有匹配到則返回-1
return false;
}
return true;
}
}
9.修改數組中的一個數使其變爲非遞減
給你一個長度爲 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
解題思路:對於給定的數組,如果出現num[i]>num[i+1],需要變更數字的位置爲i或者i+1。(1)當num[i-1]<num[i+1] eg:1(i-1) 3(i) 2(i+1),最優的變更爲num[i]=num[i+1](1 2 2) ,因爲如果變更爲num[i+1]=num[i](1 3 3)則更違反非遞減的規則。(2)當num[i-1]>num[i+1] eg:2 3 1,則只能使 num[i+1]=num[i](2 3 3)。還需要設置個變量count統計變更次數,如果次數超過1則直接返回false。
JAVA代碼:
/**
* Created by 高先森 on 2020/6/24.
*/
public class leetcode_665_NoneDecreasingArray {
public static void main(String[] args){
int[] ints={4,2,1};
System.out.println(checkPossibility(ints));
}
public static boolean checkPossibility(int[] nums) {
int len=nums.length;
int count=0;//用於標記交換的次數,超過一次爲不符合
//1.如果數組的長度小於三,只要通過一次變換一定能滿足非遞減
if(len<3)
return true;
//2.大於三時對 i-1,i,i+1位置的數字就行判斷
//1 3 2
//2 3 1
for(int i=0;i<len-1;i++){
if(nums[i]<=nums[i+1])
continue;
//存在nums[i]>nums[i+1],需要增加一次交換
count++;
if(count>1)//交換次數大於一直接返回
return false;
if(i==0)
nums[i]=nums[i+1];
//如果i>0且num[i-1]<nums[i+1],eg:1 3 2 儘量將i位置的數字置小更易滿足非遞減1 2 2 (1 3 3 更容易引起num[i+1]>num[i+2])
else if(i>0&&nums[i-1]<nums[i+1])
nums[i]=nums[i+1];
else{ //eg:2 3 1 只能將num[i+1]置爲num[i] 2 3 3才符合
nums[i+1]=nums[i];
}
}
return true;
}
}
10.最大子數組和
給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。
示例:
輸入: [-2,1,-3,4,-1,2,1,-5,4],
輸出: 6
解釋: 連續子數組 [4,-1,2,1] 的和最大,爲 6。
進階:
如果你已經實現複雜度爲 O(n) 的解法,嘗試使用更爲精妙的分治法求解。
解題思想:動態規劃:dp[i]爲到i位置最大子序列的和 dp[i]=Math.max(dp[i-1]+num[i],num[i])
JAVA代碼:
/**
* Created by 高先森 on 2020/6/24.
*/
public class leetcode_53_MaxinumSubarray {
public static void main(String[] args){
int[] ints={-2,1,-3,4,-1,2,1,-5,4};
//System.out.println(maxSubArray(ints));
System.out.println(maxSubArray1(ints));
}
//1.動態規劃:dp[i]爲到i位置最大子序列的和
//nums:{-2,1,-3,4,-1,2,1,-5,4}
//dp[i]內容:-2,1,-2,4,3,5,6,1,5
public static int maxSubArray(int[] nums) {
if(nums.length==0||nums==null)
return 0;
int[] dp=new int[nums.length];
int maxRes=nums[0];//隨機初始化
dp[0]=nums[0];
for(int i=1;i<nums.length;i++){
//dp[i]爲到i位置的最大子序列和,dp[i]的值爲到i-1位置最大子序列的和加上當前數和當前數比較的最大值
dp[i]=Math.max(dp[i-1]+nums[i],nums[i]);
if(maxRes<dp[i])
maxRes=dp[i];
}
return maxRes;
}
//2.降低空間複雜度的動態規劃,每個狀態只與前一個狀態有關,所以爲了降低空間複雜度只用一個變量來保存
public static int maxSubArray1(int[] nums) {
if(nums.length==0||nums==null)
return 0;
//sum初始化nums[0],sum用於記錄每個子序列的和,maxRes用於記錄所有子序列中的最大值
int sum=nums[0],maxRes=sum;
for(int i=1;i<nums.length;i++){
//dp[i]爲到i位置的最大子序列和,dp[i]的值爲到i-1位置最大子序列的和加上當前數和當前數比較的最大值
sum=Math.max(sum+nums[i],nums[i]);
if(maxRes<sum)//maxRes爲所有子序列中的最大子序列和
maxRes=sum;
}
return maxRes;
}
}
11.劃分字母區間使相同字母出現在同一區間
字符串 S 由小寫字母組成。我們要把這個字符串劃分爲儘可能多的片段,同一個字母只會出現在其中的一個片段。返回一個表示每個字符串片段的長度的列表。
示例 1:
輸入:S = "ababcbacadefegdehijhklij"
輸出:[9,7,8]
解釋:
劃分結果爲 "ababcbaca", "defegde", "hijhklij"。
每個字母最多出現在一個片段中。
像 "ababcbacadefegde", "hijhklij" 的劃分是錯誤的,因爲劃分的片段數較少。
解決思路:
定義數組 charAt[26] 來表示字符 字符串中每個char最後一次出現的下標。定義 left和right來表示當前區間的左邊界和右邊界。如果遇到的字符最後一次出現的位置下標大於 right, 就讓 right=charAt[i] 來拓展當前的區間。當遍歷到了當前區間的末尾時(即 i==right ),把當前區間加入答案,同時將 left 設爲 i+1 去找下一個區間。
JAVA代碼:
import java.util.ArrayList;
import java.util.List;
/**
* Created by 高先森 on 2020/6/24.
*/
public class leetcode_763_PartitionLabels {
public static void main(String[] args){
System.out.println(partitionLabels1("ababcbacadefegdehijhklij"));
}
//方法一:常規解法
public static List<Integer> partitionLabels(String S) {
int len=S.length();
//1.定義一個list存放結果
List<Integer> list=new ArrayList<>();
//2.將String轉換爲字符數組
char[] strChars=S.toCharArray();
//3.left標記當前區間的最左端,right標記當前區間的最右端,j>=left&&j<=right,通過遍歷j來擴大(區間)right的範圍
int left,right,j;
//4.尋找區間
for(int i=0;i<len;){
left=i;
right=S.lastIndexOf(strChars[left]);
j=left;
while (j<right){
j++;
right=right>S.lastIndexOf(strChars[j])?right:S.lastIndexOf(strChars[j]);
}
//將劃分的區間結果進行存儲
list.add(right-left+1);
i=right+1;//下一個子區間左邊界
}
return list;
}
//方法二:貪心思想
public static List<Integer> partitionLabels1(String S) {
//1.使用charAt數組標記每個字符在串中出現的最後位置
int[] charAt=new int[26];
List<Integer> list=new ArrayList<>();
int len=S.length();
for(int i=0;i<len;i++){
charAt[S.charAt(i)-'a']=i;
}
//2.進行最大子區間的劃分
int left=0,right=0;//left標記當前可劃分的最大子區間的左端,right爲右端
for(int i=0;i<len;i++){
//在left和right區間內的字母可能會擴大區間的右邊界 eg:defegde 開始:left=0,right=5,下一次遍歷到e,right擴充爲6
right=Math.max(right,charAt[S.charAt(i)-'a']);
if(i==right){//當前最大子區間確定完畢
list.add(right-left+1);
left=i+1;//下一個子區間的左邊界
}
}
return list;
}
}