# Leetcode 1~90(c++資結演算法)
~~~~
紅色=Hard, 黃色=Medium, 綠色=Easy
~~~~
### <font color="c1ab0c">2. Linked list</font>
#### [題目:Add Two Numbers](https://leetcode.com/problems/add-two-numbers/description/)
內建ListNode存有當前位的數字及指向下位的地址。
1. 建一個虛擬頭節點 dummy,以及一個指標 curr 指向它。(移動curr去完成dummy)
1. 建立一個變數 carry = 0。
1. 用 while 迴圈遍歷 l1 和 l2,直到都為空且 carry = 0。
1. 每次迴圈取出 l1 和 l2 當前的值(若為 nullptr 則為 0),加上 carry。
1. 計算當前位數 sum % 10,以及更新進位 carry = sum / 10。
1. 把該位數加入新 list,curr = curr->next。
1. 返回 dummy->next 即為答案。
```cpp=
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(0);
ListNode* curr = dummy;
int carry = 0;
while(l1!=nullptr || l2!=nullptr || carry!=0){
int x = (l1!=nullptr)? l1->val : 0;//若為空 則=0
int y = (l2!=nullptr)? l2->val : 0;//若為空 則=0
int sum= x + y + carry;
carry=sum/10;
curr->next = new ListNode(sum%10);
curr = curr->next;
//l1 l2如果目前不是空的就繼續前進
if(l1) l1=l1->next;
if(l2) l2=l2->next;
}
return dummy->next;
}
};
```
---
### <font color="c1ab0c">3. Sliding window</font>
#### [題目:Longest Substring Without Repeating Characters](https://leetcode.com/problems/longest-substring-without-repeating-characters/description/)
用字元會自動轉換成ASCII編碼的特性,開一個128大小的陣列去存每個字元最後一次出現的位置+1,例如'a'=97,index[97]=3,意思是字符 'a' 在位置 2,那麼下次遇到 'a' 時,left 應該跳到位置 3。
```cpp=
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int index[128] = {0}; // 初始化為 0
int maxLen = 0, left = 0;
for (int right = 0; right < s.size(); right++) {
left = max(left, index[s[right]]);
int currentLen = right - left + 1;
if (currentLen > maxLen) maxLen = currentLen;
index[s[right]] = right + 1;
}
return maxLen;
}
};
```
---
### <font color="FF5151">4. Array </font>
#### [題目:Median of Two Sorted Arrays](https://leetcode.com/problems/median-of-two-sorted-arrays/description/)
難點:O(log(m+n))
核心思路:**二分搜索**
我們需要找到一個分割點,將兩個數組分成左右兩部分,使得:
左半部分的元素個數 = 右半部分的元素個數(或相差1)
左半部分的最大值 ≤ 右半部分的最小值
```cpp=
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
// 確保 nums1 是較短的數組,減少搜索空間
if (nums1.size() > nums2.size()) {
return findMedianSortedArrays(nums2, nums1);
}
int m = nums1.size();
int n = nums2.size();
int total = m + n;
int half = (total + 1) / 2; // 左半部分應有的元素個數
int left = 0, right = m;
while (left <= right) {
// 在 nums1 中的分割點
int cut1 = (left + right) / 2;
// 在 nums2 中的分割點
int cut2 = half - cut1;
// 左半部分的最大值
int maxLeft1 = (cut1 == 0) ? INT_MIN : nums1[cut1 - 1];
int maxLeft2 = (cut2 == 0) ? INT_MIN : nums2[cut2 - 1];
int maxLeft = max(maxLeft1, maxLeft2);
// 右半部分的最小值
int minRight1 = (cut1 == m) ? INT_MAX : nums1[cut1];
int minRight2 = (cut2 == n) ? INT_MAX : nums2[cut2];
int minRight = min(minRight1, minRight2);
// 找到正確的分割點
if (maxLeft <= minRight) {
if (total % 2 == 0) {
// 偶數個元素,返回中間兩個的平均值
return (maxLeft + minRight) / 2.0;
} else {
// 奇數個元素,返回左半部分的最大值
return maxLeft;
}
}
// nums1 的分割點太靠右了
else if (maxLeft1 > minRight2) {
right = cut1 - 1;
}
// nums1 的分割點太靠左了
else {
left = cut1 + 1;
}
}
return -1; // 理論上不會到達這裡
}
};
```
---
### <font color="c1ab0c">5. 迴文子字串</font>
#### [題目:Longest Palindromic Substring](https://leetcode.com/problems/longest-palindromic-substring/)
遍歷以第i個位元為中心的迴文子字串,分別檢查偶字數中心跟奇字數中心(往兩邊字判斷是否相同), start = i - (cur_maxlen-1) / 2,是因為EX:aba時,i在b,cur_maxlen=3,起始點為i-1;
EX:abba時,i在第一個b,cur_maxlen=4,起始點為i-1。(3/2=1)
```cpp=
class Solution {
public:
string longestPalindrome(string s) {
if(s.length()==0) return "";
int start=0,maxlen=0;
for(int i=0;i<s.length();i++){
//奇數中心
int len1 = cal(s,i,i); //aba 中心是b
//偶數中心
int len2 = cal(s,i,i+1); //abba 中心是bb
int cur_maxlen=max(len1,len2); //i這個位置最大的回文長度
if(cur_maxlen>maxlen){
maxlen = cur_maxlen;
start = i- (cur_maxlen-1)/2;
}
}
return s.substr(start, maxlen);
}
int cal(string s,int left,int right){
while(left>=0 && right<s.length() && s[left]==s[right]){
left--;
right++;
}
return right-left-1;
}
};
```
---
### <font color="c1ab0c">6. 之字形重組字串</font>
#### [題目:Zigzag Conversion](https://leetcode.com/problems/zigzag-conversion/description/)
用一個陣列來儲存每一行的字元,然後模擬這個鋸齒形填充過程:
1. 建立 numRows 個字串陣列,每個代表一行
1. 用一個變數 cnt 追蹤目前行
1. 用一個變數 dir 表示目前是向下還是向上移動
1. 遍歷字串,將每個字元加到對應行
1. 當到達頂部或底部時,改變方向
```cpp=
class Solution {
public:
string convert(string s, int numRows) {
if (numRows == 1) return s;
vector<string> rows(numRows);
int dir=0;//0代表往下數 1代表往上數
int cnt=0; //走到哪row
for(char c:s){
rows[cnt]+=c;
if(dir==0) cnt++;
else cnt--;
if(cnt==(numRows-1) || cnt==0){
if(dir==0) dir=1;
else dir=0;
}
}
string result;
for(string row : rows){
result+=row;
}
return result;
}
};
```
---
### <font color="c1ab0c">7. 翻轉整數</font>
#### [題目: Reverse Integer](https://leetcode.com/problems/reverse-integer/description/)
1. 逐位取出最後一位數字
1. 在將新數字加入結果前,檢查是否會溢位
1. 溢位條件:
如果 result > INT_MAX/10,那麼 result*10 就已經超過 INT_MAX 了
如果 result == INT_MAX/10,還要檢查加上 digit 後是否超過 INT_MAX
```cpp=
class Solution {
public:
int reverse(int x) {
int result=0;
while(x!=0){
int digit=x%10;
x=x/10;
//正溢位
if(result>INT_MAX/10 || (result==INT_MAX/10 && digit>INT_MAX%10)){
return 0;
}
//負溢位
if(result<INT_MIN/10 || (result==INT_MIN/10 && digit<INT_MIN%10)){
return 0;
}
result=result*10+digit;
}
return result;
}
};
```
---
### <font color="c1ab0c">8. 字串to數字</font>
#### [題目: String to Integer (atoi)](https://leetcode.com/problems/string-to-integer-atoi/description/)
```cpp=
class Solution {
public:
int myAtoi(string s) {
if(s.empty()) return 0; //空的直接回傳
int i=0;
int n=s.length();
while(i<n&&s[i]==' '){ //略過空白字元
i++;
}
if(i==n){//全空白直接回傳
return 0;
}
int sign=1;
if(s[i]=='+'||s[i]=='-'){ //處理sign
if(s[i]=='-'){
sign=-1;
}
i++;
}
long long result=0;
while (i < n && isdigit(s[i])) {
int digit = s[i] - '0';
result = result * 10 + digit;
// 提前檢查溢出,避免 long long 也溢出
if (sign == 1 && result > INT_MAX) {
return INT_MAX;
}
if (sign == -1 && result > (long long)INT_MAX + 1) {
return INT_MIN;
}
i++;
}
// 步驟 4: 應用正負號
result *= sign;
// 最終範圍檢查(雙重保險)
if (result < INT_MIN) {
return INT_MIN;
}
if (result > INT_MAX) {
return INT_MAX;
}
return (int)result;
}
};
```
---
### <font color="green">9. 迴文判斷(數字)</font>
#### [題目: Palindrome Number](https://leetcode.com/problems/palindrome-number/description//)
1. 負數和以0結尾的數字必不是迴文
2. 反轉一半位數,根據位數奇偶性判斷是否一樣
```cpp=
class Solution {
public:
bool isPalindrome(int x) {
if(x<0) return false;
else if((x%10==0&&x!=0)) return false;
int reverse_half=0;
while(x>reverse_half){
reverse_half=reverse_half*10+x%10;
x/=10;
}
//奇數個位數 x=reverse_half/10 去掉中間位
//偶數個位數 x=reverse_half
return x==reverse_half||x==reverse_half/10;
}
};
```
---
### <font color="FF5151">10. 動態規劃</font>
#### [題目: Regular Expression Matching](https://leetcode.com/problems/regular-expression-matching/description/)
DP 狀態定義:
* dp[i][j] = true 表示字串 s[0...i-1] 能夠匹配模式 p[0...j-1]
```cpp=
class Solution {
public:
bool isMatch(string s, string p) {
int m=s.length();
int n=p.length();
vector<vector<bool>> dp(m+1,vector<bool>(n+1,false));
//初始化基礎情況
dp[0][0]=true; //空字串配空模式
//空字串與含有*
for(int j=2;j<=n;j++){
if(p[j-1]=='*'){
dp[0][j]=dp[0][j-2];
}
}
//填充dp
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
char sc=s[i-1];
char pc=p[j-1];
if(pc=='*'){
// *匹配前面的字符
char prechar = p[j-2];
//兩種情況
//1.忽略x*
dp[i][j]=dp[i][j-2];
//2.如果前面字符匹配,使用x*模式
if(prechar=='.'||prechar==sc){
dp[i][j]=dp[i][j]||dp[i-1][j];
}
}else if(pc=='.'||pc==sc){
dp[i][j]=dp[i-1][j-1];
}
}
}
return dp[m][n];
}
};
```
---
### <font color="c1ab0c">11. 兩指標</font>
#### [題目: Container With Most Water](https://leetcode.com/problems/container-with-most-water/description/)
容器的水量 = 寬度 × 高度
* 寬度 = 兩條線之間的距離
* 高度 = 兩條線中較短的那條(因為水會從短的那邊溢出)
為什麼要移動較短的線?
假設當前左指標指向較短的線:
如果移動右指標,寬度減少,高度仍受左邊較短線限制 → 面積必定變小
如果移動左指標,雖然寬度減少,但可能找到更高的線 → 有機會增加面積
```cpp=
class Solution {
public:
int maxArea(vector<int>& height) {
int left=0,right=height.size()-1,ans=0,cur_high;
while(left<right){
cur_high=min(height[left],height[right]);
ans=max(ans, cur_high*(right-left));
if(cur_high==height[right]) right--;
else left++;
}
return ans;
}
};
```
---
### <font color="c1ab0c">12. 貪心算法</font>
#### [題目: Integer to Roman](https://leetcode.com/problems/integer-to-roman/description/)
預處理映射表:將所有可能的羅馬數字組合(包括減法表示法)按數值大小排序
貪心策略:總是優先使用最大可能的羅馬數字組合
特殊情況處理:
* IV(4), IX(9), XL(40), XC(90), CD(400), CM(900) 這些減法表示法
* 避免出現 IIII, XXXX, CCCC 等超過3個重複的情況
演算法流程:
1. 遍歷映射表中的每個數值-符號對
1. 計算當前數值可以被整除多少次
1. 將對應次數的符號加入結果
1. 更新剩餘數值
```cpp=
class Solution {
public:
std::string intToRoman(int num) {
// 定義所有可能的羅馬數字組合,按數值從大到小排列
// 包含減法表示法:CM(900), CD(400), XC(90), XL(40), IX(9), IV(4)
std::vector<std::pair<int, std::string>> valueSymbols = {
{1000, "M"}, {900, "CM"}, {500, "D"}, {400, "CD"},
{100, "C"}, {90, "XC"}, {50, "L"}, {40, "XL"},
{10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"},
{1, "I"}
};
std::string result = "";
// 貪心算法:從最大的數值開始處理
for (const auto& pair : valueSymbols) {
int value = pair.first;
const std::string& symbol = pair.second;
// 計算當前數值可以使用多少次
int count = num / value;
// 將對應的羅馬數字符號加入結果
for (int i = 0; i < count; i++) {
result += symbol;
}
// 更新剩餘的數值
num %= value;
// 如果已經處理完畢,可以提前結束
if (num == 0) break;
}
return result;
}
};
```
---
### <font color="green">13. 上題的逆向</font>
#### [題目: 13. Roman to Integer](https://leetcode.com/problems/roman-to-integer/description/)
```cpp=
class Solution {
public:
int romanToInt(string s) {
unordered_map<char, int>romanMap={
{'I', 1}, {'V', 5}, {'X', 10}, {'L', 50},
{'C', 100}, {'D', 500}, {'M', 1000}
};
int result=0;
int preValue=0;
// 從右到左遍歷字符串
for (int i = s.length() - 1; i >= 0; i--) {
int currentValue = romanMap[s[i]];
// 如果當前值小於前一個值,表示是減法情況
if (currentValue < preValue) {
result -= currentValue;
} else {
result += currentValue;
}
preValue = currentValue;
}
return result;
}
};
```
---
### <font color="green">14. 相同字串前綴</font>
#### [題目: 13. Longest Common Prefix](https://leetcode.com/problems/longest-common-prefix/description/)
1. 找出最短字串
2. i=目前比較位置字元,j遍歷每個strs內的字串
```cpp=
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
int minlen=strs[0].length();
for(const string& str:strs){
minlen=min(minlen,(int)str.length());
}
for(int i=0;i<minlen;i++){
for(int j=1;j<strs.size();j++){
if(strs[0][i]!=strs[j][i]) return strs[0].substr(0,i);
}
}
return strs[0].substr(0,minlen);
}
};
```
---
### <font color="c1ab0c">15. 兩指標</font>
#### [題目: 3Sum](https://leetcode.com/problems/3sum/description/)
先排序,i遍歷每一個元素(做為第一個元素),利用left right找出剩下符合的兩個元素,太大把右邊往左移,太小則把左邊往右移
```cpp=
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++){
//跳過重複的第一位
if(i>0&&nums[i]==nums[i-1]) continue;
//雙指針
int left=i+1;
int right=nums.size()-1;
while(left<right){
int sum=nums[i]+nums[left]+nums[right];
if(sum==0){
ans.push_back({nums[i],nums[left],nums[right]});
while(left<right&&nums[left]==nums[left+1]){//左邊跳過重複的
left++;
}
while(left<right&&nums[right]==nums[right-1]){//右邊跳過重複的
right--;
}
left++;
right--;
}else if(sum>0){
right--;
}else{
left++;
}
}
}
return ans;
}
};
```
---
### <font color="c1ab0c">16. 兩指標</font>
#### [題目: 3Sum Closest](https://leetcode.com/problems/3sum-closest/description/)
先排序,i遍歷每一個元素(做為第一個元素),利用left right找出剩下最接近target的兩個元素,太大把右邊往左移,太小則把左邊往右移
```cpp=
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
sort(nums.begin(),nums.end());
//初始化
int closest=nums[0]+nums[1]+nums[2];
int minDiff=abs(target-closest);
for(int i=0;i<nums.size();i++){
//跳過重複的第一位
if(i>0&&nums[i]==nums[i-1]) continue;
//雙指針
int left=i+1;
int right=nums.size()-1;
while(left<right){
int sum=nums[i]+nums[left]+nums[right];
int curDiff=abs(target-sum);
if(curDiff<minDiff){
closest=sum;
minDiff=curDiff;
}
if(sum==target){
return target;
}else if(sum>target){
right--;
}else{
left++;
}
}
}
return closest;
}
};
```
---
### <font color="c1ab0c">17. 字串組合</font>
#### [題目: Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number/description/)
* 核心思想:
從空字串開始,對每個數字的每個字母進行擴展
每次處理一個數字,將現有的所有組合與新字母結合
執行過程(以 "23" 為例):
初始: [""]
處理 '2': ["a", "b", "c"]
處理 '3': ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]
```cpp=
class Solution {
public:
vector<string> letterCombinations(string digits) {
if(digits.empty()) return {};
vector<string> ans={""};
unordered_map<char, string> mapping = {
{'2', "abc"}, {'3', "def"}, {'4', "ghi"}, {'5', "jkl"},
{'6', "mno"}, {'7', "pqrs"}, {'8', "tuv"}, {'9', "wxyz"}
};
for(const char& digit:digits){
vector<string> temp;
string letters=mapping[digit];
for(const string& combination:ans){
for(char letter:letters){
temp.push_back(combination+letter);
}
}
ans=temp;
}
return ans;
}
};
```
---
### <font color="c1ab0c">18. 兩指標</font>
#### [題目: 4Sum](https://leetcode.com/problems/3sum-closest/description/)
1. 排序 + 雙指針法:
先將陣列排序,這樣可以使用雙指針技巧
使用四層結構:兩層迴圈固定前兩個數字,雙指針找後兩個數字
2. 避免重複解:
在每一層都跳過重複的數字
當找到一組解後,移動指針時也要跳過重複值
3. 處理溢位問題:
使用 long long 來避免整數溢位
特別是在計算 target - nums[i] - nums[j] 時
```cpp=
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
int n = nums.size();
// 如果陣列長度小於4,無法形成四元組
if (n < 4) return result;
sort(nums.begin(), nums.end());
// 第一層迴圈:固定第一個數字
for (int i = 0; i < n - 3; i++) {
// 跳過重複的第一個數字
if (i > 0 && nums[i] == nums[i - 1]) continue;
// 第二層迴圈:固定第二個數字
for (int j = i + 1; j < n - 2; j++) {
// 跳過重複的第二個數字
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
// 使用雙指針找剩下的兩個數字
int left = j + 1;
int right = n - 1;
// 計算目標值(注意溢位問題)
long long target2 = (long long)target - nums[i] - nums[j];
while (left < right) {
long long sum = (long long)nums[left] + nums[right];
if (sum == target2) {
// 找到一組解
result.push_back({nums[i], nums[j], nums[left], nums[right]});
// 跳過重複的左指針值
while (left < right && nums[left] == nums[left + 1]) left++;
// 跳過重複的右指針值
while (left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
}
else if (sum < target2) {
left++;
}
else {
right--;
}
}
}
}
return result;
}
};
```
---
### <font color="c1ab0c">19. 兩指標+Linked list</font>
#### [題目: Remove Nth Node From End of List](https://leetcode.com/problems/remove-nth-node-from-end-of-list/description/)
這個方法的思路如下:
1. 設定虛擬節點 (Dummy Node):在鏈結串列的頭部 head 前面創建一個虛擬的 dummy 節點。這個節點的目的是為了簡化邊界情況,特別是當要被移除的節點正好是原始的頭部節點時,虛擬節點可以確保每個節點(包括頭部)都有一個前驅節點,讓刪除操作的邏輯保持一致。
1. 初始化快慢指標:我們使用兩個指標,fast(快指標)和 slow(慢指標),兩者一開始都指向這個 dummy 節點。
1. 拉開指標間距:讓 fast 指標先向前移動 n + 1 步。這樣做會在 fast 和 slow 指標之間創造一個 n 個節點的「窗口」或「間距」。
1. 同步移動指標:接著,同時移動 fast 和 slow 指標,每次都向前一步。持續這個過程,直到 fast 指標到達鏈結串列的末尾(即 fast 變為 nullptr)。
1. 定位與刪除:當 fast 指標到達終點時,由於它們之間固定的 n 個節點的間距,slow 指標此時會正好停在要被刪除節點的前一個節點上。
1. 執行刪除:要刪除目標節點,我們只需要修改 slow 指標的 next 指向,讓它跳過目標節點,直接指向目標節點的下一個節點即可 (slow->next = slow->next->next;)。同時,為了防止記憶體洩漏,最好將被刪除的節點釋放掉。
1. 返回結果:最後,返回 dummy->next。這就是修改後的新鏈結串列的頭部。
```cpp=
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0);
dummy->next=head;
ListNode* fast = dummy;
ListNode* slow = dummy;
for(int i=0;i<=n;i++){
fast=fast->next;
}
while(fast!=nullptr){
fast=fast->next;
slow=slow->next;
}
ListNode* temp=slow->next;
slow->next=slow->next->next;
delete temp;
return dummy->next;
}
};
```
---
### <font color="c1ab0c">20. Stack</font>
#### [題目: Valid Parentheses](https://leetcode.com/problems/valid-parentheses/description/)
遇到閉括號 → 查字典找對應的開括號
檢查堆疊頂端是否匹配
匹配就彈出,不匹配就返回 false
```cpp=
class Solution {
public:
bool isValid(string s) {
unordered_map<char, char> closeToOpen = {
{')', '('},
{'}', '{'},
{']', '['}
};
stack<char> st;
for (char c : s) {
// 如果是閉括號
if (closeToOpen.find(c) != closeToOpen.end()) {
// 檢查堆疊是否為空,或頂端是否匹配
if (st.empty() || st.top() != closeToOpen[c]) {
return false;
}
st.pop();
}
// 如果是開括號,直接推入堆疊
else {
st.push(c);
}
}
return st.empty();
}
};
```
---
### <font color="green">21. Linked list合併</font>
#### [題目: Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/description/)
這個問題的關鍵是雙指標技巧:
1. 用兩個指標分別指向兩個已排序的鏈結串列
1. 比較當前節點值,選擇較小的連接到結果中
1. 移動對應指標,重複過程
```cpp=
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* dummy = new ListNode(0);
ListNode* cur=dummy;
while(list1!=nullptr&&list2!=nullptr){
if(list1->val<=list2->val){
cur->next=list1;
list1=list1->next;
}else{
cur->next=list2;
list2=list2->next;
}
cur=cur->next;
}
//處理較長的list還沒被加入的部分
if(list1!=nullptr){
cur->next=list1;
}else if(list2!=nullptr){
cur->next=list2;
}
return dummy->next;
}
};
```
---
### <font color="c1ab0c">22. 遞迴</font>
#### [題目: Generate Parentheses](https://leetcode.com/problems/generate-parentheses/description/)
```cpp=
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
cal(result,"",n,0,0);
return result;
}
void cal(vector<string>& result,string cur,int n,int open,int close){
// 終止條件:當字串長度等於 2 * n,表示找到一個有效組合
if(open+close==2*n){
result.push_back(cur);
return;
}
// 規則一:如果左括號的數量還沒達到上限 n,就可以添加一個左括號
if(open<n) cal(result,cur+"(",n,open+1,close);
// 規則二:如果右括號的數量小於左括號,就可以添加一個右括號以進行匹配
if(open>close) cal(result,cur+")",n,open,close+1);
}
};
```
---
### <font color="c1ab0c">23. Priority queue + Linked list</font>
#### [題目: Merge k Sorted Lists](https://leetcode.com/problems/merge-k-sorted-lists/description/)
核心思想:使用優先佇列(最小堆)來追蹤每個鏈表的當前最小節點
時間複雜度:O(N log k),其中 N 是所有節點總數
空間複雜度:O(k),堆的大小
優點:效率高,容易理解
工作原理:
1. 將每個鏈表的第一個節點放入最小堆
1. 每次從堆中取出最小值節點,加入結果鏈表
1. 如果該節點有下一個節點,將下一個節點加入堆
1. 重複直到堆為空
```cpp=
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty()) return{};
auto compare = [](ListNode* a, ListNode* b) {
return a->val > b->val;//使最小的在最前面
};
priority_queue<ListNode*, vector<ListNode*>, decltype(compare)>
minHeap(compare);
for(ListNode* list:lists){
if(list!=nullptr) minHeap.push(list);
}
ListNode* dummy = new ListNode(0);
ListNode* cur=dummy;
while(!minHeap.empty()){
//目前最小
ListNode* minNode=minHeap.top();
minHeap.pop();
cur->next=minNode;
cur=cur->next;
//如果目前最小還有下一個就更新
if(minNode->next!=nullptr){
minHeap.push(minNode->next);
}
}
return dummy->next;
}
};
```
---
### <font color="c1ab0c">24. Linked List</font>
#### [題目: Swap Nodes in Pairs](https://leetcode.com/problems/swap-nodes-in-pairs/)
1. 用prev指針追蹤當前要交換的節點對的前一個節點
1. 逐對交換節點
```cpp=
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy = new ListNode(0);
ListNode* prev=dummy;
dummy->next=head;
while(prev->next!=nullptr && prev->next->next!=nullptr){//還有兩個可以交換
ListNode* first=prev->next;
ListNode* sec=prev->next->next;
//交換
prev->next=sec;
first->next=sec->next;
sec->next=first;
//移動prev
prev=first;
}
return dummy->next;
}
};
```
---
### <font color="FF5151">25. Linked List反轉(難)</font>
#### [題目: Reverse Nodes in k-Group](https://leetcode.com/problems/reverse-nodes-in-k-group/description/)
```cpp=
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if(!head||k==1) return head;
ListNode* dummy =new ListNode(0);
dummy->next=head;
ListNode* prev=dummy;
int length=getLength(head);
//確保剩下的長度還超過k即要處理
while(length>=k){
//目前組的開始和結束位置
ListNode* start=prev->next;
ListNode* end=start;
for(int i=1;i<k;i++){
end=end->next;
}
//下一組開始處理
ListNode* nextStart=end->next;
reverseGroup(start,end);
// 連接反轉後的組
// 注意:反轉後 end 變成了組的開頭,start 變成了組的結尾
prev->next = end;
start->next = nextStart;
// 更新指針,準備處理下一組
prev = start;
length -= k;
}
return dummy->next;
}
void reverseGroup(ListNode* start,ListNode* end){
ListNode* prev = end->next; // 這將成為反轉後第一個節點的 next
ListNode* curr = start;
// 反轉過程,直到處理完 end 節點
while (prev != end) {
ListNode* next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
}
int getLength(ListNode* head) {
int length = 0;
while (head) {
length++;
head = head->next;
}
return length;
}
};
```
---
### <font color="green">26. 移除重複</font>
#### [題目: Remove Duplicates from Sorted Array](https://leetcode.com/problems/remove-duplicates-from-sorted-array/description/)
```cpp=
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.empty()) return 0;
int cur=1;
for(int num:nums){
if(num!=nums[cur-1]){
nums[cur]=num;
cur++;
}
}
return cur;
}
};
```
---
### <font color="green">27. 移除指定的數字</font>
#### [題目: Remove Element](https://leetcode.com/problems/remove-element/description/)
```cpp=
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int cur=0;
for(int num:nums){
if(num!=val){
nums[cur]=num;
cur++;
}
}
return cur;
}
};
```
---
### <font color="green">28. 找第二個字串是否有出現在第一個字串中</font>
#### [題目:Find the Index of the First Occurrence in a String](https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/)
```cpp=
class Solution {
public:
int strStr(string haystack, string needle) {
for(int i=0;i<haystack.length();i++){
if(haystack[i]==needle[0]){ //出現第二個字的開頭
if(cal(haystack,needle,i,needle.length())) return i;
}
}
return -1;
}
bool cal(string& haystack,string& neddle,int cur,int len){
for(int i=0;i<len;i++){ //檢查到第二個字的長度 是否都相同
if(haystack[i+cur]!=neddle[i]) return false;
}
return true;
}
};
```
---
### <font color="c1ab0c">29. 除法</font>
#### [題目: Divide Two Integers](https://leetcode.com/problems/divide-two-integers/description/)
為什麼用負數?
INT_MIN = -2147483648
INT_MAX = 2147483647
abs(INT_MIN) = 2147483648 → 無法用int表示!
但所有正數都可以轉換為負數
* 算法邏輯(負數環境)
轉換:將所有數轉為負數
比較:absDividend <= absDivisor(都是負數)
倍增:tempDivisor += tempDivisor(相當於乘以2)
quotient=商
```cpp=
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy = new ListNode(0);
ListNode* prev=dummy;
dummy->next=head;
while(prev->next!=nullptr && prev->next->next!=nullptr){//還有兩個可以交換
ListNode* first=prev->next;
ListNode* sec=prev->next->next;
//交換
prev->next=sec;
first->next=sec->next;
sec->next=first;
//移動prev
prev=first;
}
return dummy->next;
}
};
```
---
### <font color="FF5151">25. 子字串(sliding window)</font>
#### [題目: Substring with Concatenation of All Words](https://leetcode.com/problems/substring-with-concatenation-of-all-words/description/)
解題思路:
1. 預處理:統計 words 陣列中每個單詞的出現頻率
1. 滑動窗口:由於所有單詞長度相同,我們只需要嘗試從 0 到 wordLen-1 的起始位置
1. 向右擴展窗口,每次加入一個單詞
1. 如果單詞有效(存在於 words 中),將其加入窗口頻率表
1. 如果某個單詞出現次數過多,從左側縮小窗口
1. 如果遇到無效單詞,重置整個窗口
有效性檢查:當 validWords 等於 wordCount 時,找到一個有效的連接
i為何只需從0到len-1:
假設 wordLen = 3,字串 s = "abcdefghijklmno"
我們可以將字串按照以下方式分組:
* 起始位置 0:
位置: 0 3 6 9 12
abc def ghi jkl mno
↑ ↑ ↑ ↑ ↑
* 起始位置 1:
位置: 1 4 7 10 13
bcd efg hij klm ...
↑ ↑ ↑ ↑
* 起始位置 2:
位置: 2 5 8 11 14
cde fgh ijk lmn ...
↑ ↑ ↑ ↑
* 起始位置 3:
位置: 3 6 9 12
def ghi jkl mno
↑ ↑ ↑ ↑
可發現3=0往右一個單詞(重複)
```cpp=
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> result;
if (s.empty() || words.empty()) return result;
int wordLen = words[0].length(); // 每個單詞的長度
int wordCount = words.size(); // 單詞的總數量
int totalLen = wordLen * wordCount; // 所有單詞連接後的總長度
if (s.length() < totalLen) return result;
// 統計每個單詞出現的頻率
unordered_map<string, int> wordFreq;
for (const string& word : words) {
wordFreq[word]++;
}
// 嘗試從每個可能的起始位置開始(0到wordLen-1)
for (int i = 0; i < wordLen; i++) {
int left = i; // 滑動窗口左邊界
int right = i; // 滑動窗口右邊界
unordered_map<string, int> windowFreq; // 當前窗口中單詞的頻率
int validWords = 0; // 當前窗口中有效單詞的數量
while (right + wordLen <= s.length()) {
// 取出下一個單詞
string word = s.substr(right, wordLen);
right += wordLen;
if (wordFreq.count(word)) {
windowFreq[word]++;
validWords++;
// 如果某個單詞出現次數超過要求,從左側縮小窗口
while (windowFreq[word] > wordFreq[word]) {
string leftWord = s.substr(left, wordLen);
windowFreq[leftWord]--;
validWords--;
left += wordLen;
}
// 如果窗口中的有效單詞數量正好等於要求的數量
if (validWords == wordCount) {
result.push_back(left);
}
} else {
// 如果遇到不在words陣列中的單詞,重置窗口
windowFreq.clear();
validWords = 0;
left = right;
}
}
}
return result;
}
};
```
---
### <font color="c1ab0c">31. 找下個字典排序</font>
#### [題目:Next Permutation](https://leetcode.com/problems/next-permutation/description/)
```cpp=
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n = nums.size();
// 步驟 1: 從右往左找到第一個 nums[i] < nums[i+1] 的位置
int i = n - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
// 步驟 2: 如果找到了這樣的 i,從右往左找第一個比 nums[i] 大的數
if (i >= 0) {
int j = n - 1;
while (nums[j] <= nums[i]) {
j--;
}
// 步驟 3: 交換 nums[i] 和 nums[j]
swap(nums[i], nums[j]);
}
// 步驟 4: 將 i+1 到末尾的部分反轉
reverse(nums.begin() + i + 1, nums.end());
}
};
```
---
### <font color="FF5151">32. 子字串(sliding window)</font>
#### [題目: Substring with Concatenation of All Words](https://leetcode.com/problems/substring-with-concatenation-of-all-words/description/)
狀態定義:dp[i] 表示以索引 i 結尾的最長有效括號子字串長度
狀態轉移:
只有當 s[i] == ')' 時才可能形成有效括號
情況1:s[i-1] == '(' 直接配對 ()
* dp[i] = dp[i-2] + 2
情況2:
當我們遇到連續的 '))' 時,需要找到與當前 ')' 匹配的 '('。
關鍵公式解析:
cppint matchIndex = i - dp[i-1] - 1;
為什麼這樣計算?
i = 當前 ')' 的位置
dp[i-1] = 前一個位置結尾的有效括號長度
i - dp[i-1] = 跳過前面已經配對的括號部分
i - dp[i-1] - 1 = 再往前一位,這就是可能與當前 ')' 配對的 '(' 位置
```cpp=
class Solution {
public:
int longestValidParentheses(string s) {
int n=s.length();
if(n<=1) return 0;
//dp[i] 表示以索引 i 結尾的最長有效括號子字串長度
vector<int> dp(n,0);
int maxlen=0;
for(int i=1;i<n;i++){
if(s[i]==')'){
if(s[i-1]=='('){
dp[i]=(i>2? dp[i-2]:0)+2;
}else if(dp[i-1]>0){
int matchindex=i-dp[i-1]-1;
if(matchindex>=0&&s[matchindex]=='('){
dp[i] = dp[i-1] + 2 + (matchindex > 0 ? dp[matchindex-1] : 0);
}
}
}
maxlen=max(maxlen,dp[i]);
}
return maxlen;
}
};
```
---
### <font color="c1ab0c">33. 二分搜</font>
#### [題目:Search in Rotated Sorted Array](https://leetcode.com/problems/search-in-rotated-sorted-array/description/)
在旋轉的排序陣列中,對於任意中點,左半部分或右半部分至少有一邊是完全有序的。
```cpp=
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0,right=nums.size()-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]==target) return mid;
if(nums[left]<=nums[mid]){//左半部分有序
if(nums[left]<=target && target<nums[mid]){
right=mid-1; //往左搜
}else{
left=mid+1; //往右搜
}
}else{//右半部分有序
if(nums[mid]<target && target<=nums[right]){
left=mid+1; //往右搜
}else{
right=mid-1; //往左搜
}
}
}
return -1;
}
};
```
---
### <font color="green">35. 二分搜</font>
#### [題目: Search Insert Position](https://leetcode.com/problems/search-insert-position/description/)
```cpp=
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left=0,right=nums.size()-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]==target) return mid;
else if(nums[mid]>target) right=mid-1;
else left=mid+1;
}
return left;
}
};
```
---
### <font color="c1ab0c">36. 數獨</font>
#### [題目:Valid Sudoku](https://leetcode.com/problems/valid-sudoku/description/)
使用三個二維布林陣列分別記錄行、列、子盒中數字的出現狀況
子盒編號計算公式:(i/3) * 3 + (j/3)
```cpp=
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
//開3個二維陣列分別記錄行/列/3*3box出現過的數字0~9
vector<vector<bool>> rows(9,vector<bool>(10,false));
vector<vector<bool>> columns(9,vector<bool>(10,false));
vector<vector<bool>> boxs(9,vector<bool>(10,false));
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
char c=board[i][j];
if(c=='.') continue;
int boxindex=(i/3)*3+(j/3);
int num=c-'0'; //轉成數字
if(rows[i][num]||columns[j][num]||boxs[boxindex][num]){
return false;
}
rows[i][num]=true;
columns[j][num]=true;
boxs[boxindex][num]=true;
}
}
return true;
}
};
```
---
### <font color="FF5151">37. 解數獨</font>
#### [題目: Sudoku Solver](https://leetcode.com/problems/sudoku-solver/description/)
1. **主要策略**
使用遞歸和回溯的方法
逐一處理每個空格子(標記為 '.')
對每個空格子嘗試放入數字 1-9
如果某個數字符合規則,就放入並繼續求解
如果發現無解,就回溯並嘗試下一個數字
2. **關鍵函數**
* solve(board):
找到第一個空格子
嘗試放入數字 1-9
遞歸求解剩餘部分
如果無解則回溯
* isValid(board, row, col, num):
檢查在指定位置放入數字是否符合數獨規則
檢查同一行是否有重複
檢查同一列是否有重複
檢查同一 3x3 子方格是否有重複
```cpp=
class Solution {
public:
void solveSudoku(vector<vector<char>>& board) {
solve(board);
}
bool solve(vector<vector<char>>& board) {
// 找到第一個空的格子
for (int row = 0; row < 9; row++) {
for (int col = 0; col < 9; col++) {
if (board[row][col] == '.') {
// 嘗試放入數字 1-9
for (char num = '1'; num <= '9'; num++) {
if (isValid(board, row, col, num)) {
board[row][col] = num;
// 遞歸求解剩餘部分
if (solve(board)) {
return true;
}
// 回溯:如果當前選擇導致無解,則撤銷選擇
board[row][col] = '.';
}
}
// 如果數字 1-9 都不能放入此位置,返回 false
return false;
}
}
}
// 所有格子都已填滿,求解成功
return true;
}
bool isValid(vector<vector<char>>& board, int row, int col, char num) {
// 檢查行
for (int j = 0; j < 9; j++) {
if (board[row][j] == num) {
return false;
}
}
// 檢查列
for (int i = 0; i < 9; i++) {
if (board[i][col] == num) {
return false;
}
}
// 檢查 3x3 子方格
int startRow = (row / 3) * 3;
int startCol = (col / 3) * 3;
for (int i = startRow; i < startRow + 3; i++) {
for (int j = startCol; j < startCol + 3; j++) {
if (board[i][j] == num) {
return false;
}
}
}
return true;
}
};
```
---
### <font color="c1ab0c">38. 遞迴</font>
#### [題目:Count and Say](https://leetcode.com/problems/count-and-say/description/)
用cur紀錄當前要看的字串,記得最後處理最後一組。
```cpp=
class Solution {
public:
string countAndSay(int n) {
if(n == 1) return "1";
string cur = countAndSay(n-1);
int cnt = 1;
string ans;
for(int i = 1; i < cur.length(); i++) {
if(cur[i] == cur[i-1]) {
cnt++;
} else {
ans += to_string(cnt);
ans += cur[i-1];
cnt = 1;
}
}
// 處理最後一組
ans += to_string(cnt);
ans += cur[cur.length()-1];
return ans;
}
};
```
---
### <font color="c1ab0c">39. 回溯</font>
#### [題目:Combination Sum](https://leetcode.com/problems/combination-sum/description/)
核心概念
狀態空間樹:每個節點代表一個決策點(選擇或不選擇某個數字)
剪枝優化:當當前數字大於剩餘目標時,直接跳出循環
避免重複:通過start參數確保只考慮當前位置及之後的候選數字
算法步驟
1. 排序:先對候選數組排序,便於剪枝
1. 遞歸探索:
如果target == 0,找到一個有效組合
否則嘗試每個候選數字
由於可以重複使用,遞歸時start保持為當前索引i
1. 回溯:撤銷當前選擇,嘗試下一個可能性
```cpp=
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> result;
vector<int> current;
sort(candidates.begin(),candidates.end());
backtrack(candidates, target, 0, current, result);
return result;
}
void backtrack(vector<int>& candidates, int target, int start,
vector<int>& current, vector<vector<int>>& result){
// 基礎情況:如果目標為0,找到一個有效組合
if (target == 0) {
result.push_back(current);
return;
}
for(int i=start;i<candidates.size();i++){
// 剪枝:如果當前數字大於剩餘目標,後面的數字也不可能
if (candidates[i] > target) {
break;
}
// 選擇當前數字
current.push_back(candidates[i]);
// 遞歸:注意這裡start仍為i,因為可以重複使用同一個數字
backtrack(candidates, target - candidates[i], i, current, result);
// 回溯:撤銷選擇
current.pop_back();
}
}
};
```
---
### <font color="c1ab0c">40. 回溯</font>
#### [題目:Combination Sum II](https://leetcode.com/problems/combination-sum-ii/description//)
邏輯同上題
```cpp=
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<vector<int>> ans;
vector<int> cur;
sort(candidates.begin(), candidates.end());
backtrack(candidates, target, 0, cur, ans);
return ans;
}
void backtrack(vector<int>& candidates, int target,
int start, vector<int>& cur, vector<vector<int>>& ans) {
if (target == 0) {
ans.push_back(cur);
return;
}
for (int i = start; i < candidates.size(); i++) {
if (candidates[i] > target) break;
//刪除重複
if (i > start && candidates[i] == candidates[i-1]) {
continue;
}
cur.push_back(candidates[i]);
backtrack(candidates, target - candidates[i], i + 1, cur, ans);
cur.pop_back();
}
}
};
```
---
### <font color="FF5151">41. Cyclic Sort</font>
#### [題目: first-missing-positive](https://leetcode.com/problems/first-missing-positive/description/)
**演算法步驟:**
重新排列陣列:將每個正整數 i 放到索引 i-1 的位置
只考慮範圍在 [1, n] 內的數字
使用 while 迴圈確保每個數字都放到正確位置
尋找缺失的數字:遍歷陣列,找到第一個不等於 i+1 的位置
關鍵點:
時間複雜度:O(n) - 雖然有嵌套迴圈,但每個元素最多被移動一次
空間複雜度:O(1) - 只使用常數額外空間
交換條件:nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]
數字必須是正數且在有效範圍內
目標位置的數字不能已經是正確的數字(避免無限迴圈)
範例追蹤:
對於 [3,4,-1,1]:
1. 將 3 放到索引 2:[-1,4,3,1]
1. 將 4 放到索引 3:[-1,1,3,4]
1. 將 1 放到索引 0:[1,-1,3,4]
1. 檢查:索引 1 的值是 -1,不等於 2,所以答案是 2
```cpp=
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n=nums.size();
for(int i=0;i<n;i++){
// 數字 i 應該放在索引 i-1 的位置
while(nums[i]>0&&nums[i]<=n&&nums[i]!=nums[nums[i]-1]){
swap(nums[i], nums[nums[i] - 1]);
}
}
for(int i=0;i<n;i++){
if(nums[i]!=i+1){
return i+1;
}
}
return n+1;
}
};
```
---
### <font color="FF5151">42. 兩指針</font>
#### [題目: Trapping rain water](https://leetcode.com/problems/trapping-rain-water/)
太難了不知道怎麼解釋
[看別人的解](https://englishandcoding.pixnet.net/blog/post/31607785)
```cpp=
class Solution {
public:
int trap(vector<int>& height) {
int max_left=0,max_right=0;
int i=0,j=height.size()-1,water=0;
while(i<j){
max_left=max(height[i],max_left);
max_right=max(height[j],max_right);
if(max_left<max_right){
water+=min(max_left,max_right)-height[i];
i++;
}else{
water+=min(max_left,max_right)-height[j];
j--;
}
}
return water;
}
};
```
---
### <font color="c1ab0c">43. 字串乘法(不能直接轉數字)</font>
#### [題目:multiply-strings](https://leetcode.com/problems/multiply-strings/description/)
核心思路
* 位置映射:對於 num1[i] 和 num2[j] 的乘積,結果會影響到位置 i+j 和 i+j+1
* 逐位相乘:從右到左遍歷兩個字符串,計算每一位的乘積
* 處理進位:將乘積加到對應位置,並處理進位
詳細步驟
初始化:創建長度為 m+n 的結果數組(兩個數相乘最多產生 m+n 位)
雙重循環:
外層循環遍歷 num1(從右到左)
內層循環遍歷 num2(從右到左)
計算乘積:
mul = (num1[i] - '0') * (num2[j] - '0')
位置 p1 = i + j(高位),p2 = i + j + 1(低位)
處理進位:
sum = mul + result[p2](加上之前的結果)
result[p2] = sum % 10(當前位)
result[p1] += sum / 10(進位)
```cpp=
class Solution {
public:
string multiply(string num1, string num2) {
// 處理特殊情況
if (num1 == "0" || num2 == "0") {
return "0";
}
int m = num1.size();
int n = num2.size();
vector<int> ans(m + n, 0);
for (int i = m - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
int mul = (num1[i] - '0') * (num2[j] - '0');
int p1 = i + j; // 高位位置
int p2 = i + j + 1; // 低位位置
int sum = ans[p2] + mul;
ans[p2] = sum % 10; // 設置當前位
ans[p1] += sum / 10; // 累加進位到高位
}
}
string result = "";
for (int i = 0; i < ans.size(); i++) {
// 跳過前導零
if (!(result.empty() && ans[i] == 0)) {
result += to_string(ans[i]);
}
}
return result.empty() ? "0" : result;
}
};
```
---
### <font color="FF5151">44. dp</font>
#### [題目: wildcard-matching](https://leetcode.com/problems/wildcard-matching/description/)
動態規劃狀態定義:
dp[i][j] 表示字串 s 的前 i 個字符是否能匹配模式 p 的前 j 個字符
狀態轉移:
1. 如果 p[j-1] == '*':
* 可以匹配空序列:dp[i][j] = dp[i][j-1]
* 可以匹配任何字符:dp[i][j] = dp[i-1][j]
2. 如果 p[j-1] == '?' 或 p[j-1] == s[i-1]:
* 字符匹配:dp[i][j] = dp[i-1][j-1]
邊界條件:
dp[0][0] = true(空字串匹配空模式)
處理模式開頭的連續 * 字符
```cpp=
class Solution {
public:
bool isMatch(string s, string p) {
//dp[i][j] 是指s的前i個字是否匹配p的前j模式
vector<vector<bool>> dp(s.length()+1,vector<bool>(p.length()+1,false));
//空字串匹配空模式
dp[0][0]=true;
//空字串匹配連續*
for(int i=1;i<=p.length();i++){
if(p[i-1]=='*'){
dp[0][i]=dp[0][i-1];
}
}
for(int i=1;i<=s.length();i++){
for(int j=1;j<=p.length();j++){
if(p[j-1]=='*'){
dp[i][j]=dp[i-1][j]||dp[i][j-1];
}else if(p[j-1]=='?'||p[j-1]==s[i-1]){
dp[i][j]=dp[i-1][j-1];
}
}
}
return dp[s.length()][p.length()];
}
};
```
---
### <font color="c1ab0c">45. 貪心</font>
#### [題目:Jump game II](https://leetcode.com/problems/jump-game-ii/description/)
這個算法的關鍵是理解「跳躍邊界」的概念:
currentEnd 代表當前跳躍次數下能到達的最遠位置
farthest 代表如果再跳一次能到達的最遠位置
當我們到達 currentEnd 時,必須進行下一次跳躍
```cpp=
class Solution {
public:
int jump(vector<int>& nums) {
int n=nums.size();
if(n<=1) return 0;
int jump=0; //跳幾次
int currentEnd=0; //目前可跳最遠
int farthest=0; //下次跳最遠
//不需要檢查最後一個,因為已經到了
for(int i=0;i<n-1;i++){
farthest=max(farthest,i+nums[i]);
//如果到達目前可跳最遠
if(i==currentEnd){
jump++;
currentEnd=farthest;
//如果已經能到達最後一個位置就提前結束
if(currentEnd>=n-1){
break;
}
}
}
return jump;
}
};
```
---
### <font color="c1ab0c">46. 回溯</font>
#### [題目:Permutations](https://leetcode.com/problems/permutations/description/)
遞歸情況:
* 遍歷所有數字
* 跳過已使用的數字
* 選擇未使用的數字,標記為已使用
* 遞歸生成剩餘位置的排列
* 回溯:撤銷選擇,標記為未使用
```cpp=
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> ans;
vector<bool> used(nums.size(),false);
vector<int> cur;
backtrack(nums,cur,used,ans);
return ans;
}
void backtrack(vector<int>& nums, vector<int>& cur,
vector<bool>& used, vector<vector<int>>& ans){
//長度一樣加到結果
if(cur.size()==nums.size()){
ans.push_back(cur);
return;
}
for(int i=0;i<nums.size();i++){
//已用過就跳過
if(used[i]) continue;
cur.push_back(nums[i]);
used[i]=true;
backtrack(nums,cur,used,ans);
//撤銷選擇
cur.pop_back();
used[i]=false;
}
return;
}
};
```
---
### <font color="c1ab0c">47. 回溯</font>
#### [題目:Permutations II](https://leetcode.com/problems/permutations-ii/description/)
邏輯同上題,但需要確保相同的數字照順序使用,才不會有重複。
```cpp=
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> ans;
vector<bool> used(nums.size(),false);
vector<int> cur;
sort(nums.begin(),nums.end());
backtrack(nums,cur,used,ans);
return ans;
}
void backtrack(vector<int>& nums, vector<int>& cur,
vector<bool>& used, vector<vector<int>>& ans){
//長度一樣加到結果
if(cur.size()==nums.size()){
ans.push_back(cur);
return;
}
for(int i=0;i<nums.size();i++){
//已用過就跳過
if(used[i]) continue;
// 如果當前元素與前一個元素相同,且前一個元素未被使用,則跳過
// 這確保同一層級中相同的數字按順序使用,避免重複排列
if (i > 0 && nums[i] == nums[i-1] && !used[i-1]) {
continue;
}
cur.push_back(nums[i]);
used[i]=true;
backtrack(nums,cur,used,ans);
//撤銷選擇
cur.pop_back();
used[i]=false;
}
return;
}
};
```
---
### <font color="c1ab0c">48. 翻轉矩陣</font>
#### [題目:Rotate Image](https://leetcode.com/problems/rotate-image/description/)
先轉置再反轉每一行
```cpp=
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n=matrix.size();
// 步驟1:轉置矩陣 (沿主對角線翻轉)
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
swap(matrix[i][j],matrix[j][i]);
}
}
// 步驟2:反轉每一行
for (int i = 0; i < n; i++) {
reverse(matrix[i].begin(), matrix[i].end());
}
return ;
}
};
```
---
### <font color="c1ab0c">49. 字串分組</font>
#### [題目:Group Anagrams](https://leetcode.com/problems/group-anagrams/description/)
原理: 對每個字串進行排序,重新排列字母的字串排序後會相同
步驟:
1. 對每個字串排序作為鍵值
1. 使用雜湊表 (hash map) 將具有相同排序鍵值的字串分組
1. 收集所有分組作為結果
```cpp=
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string,vector<string>> groups;
for(string str:strs){
string key=str;
sort(key.begin(),key.end());
groups[key].push_back(str);
}
vector<vector<string>> ans;
for(auto& a:groups){
ans.push_back(a.second);
}
return ans;
}
};
```
---
### <font color="c1ab0c">50. 次方</font>
#### [題目:Pow(x, n)](https://leetcode.com/problems/powx-n/description/)
```cpp=
class Solution {
public:
double myPow(double x, int n) {
// 處理邊界情況
if (n == 0) return 1.0;
if (x == 0) return 0.0;
if (x == 1) return 1.0;
if (x == -1) return (n % 2 == 0) ? 1.0 : -1.0;
// 將 n 轉換為 long long 以避免整數溢出
long long longN = n;
// 如果 n 是負數,將問題轉換為 1/x^(-n)
if (longN < 0) {
x = 1.0 / x;
longN = -longN;
}
return fastPow(x, longN);
}
double fastPow(double x, long long n) {
if (n == 0) return 1.0;
double half = fastPow(x, n / 2);
if (n % 2 == 0) {
// n 是偶數: x^n = (x^(n/2))^2
return half * half;
} else {
// n 是奇數: x^n = x * (x^(n/2))^2
return x * half * half;
}
}
};
```
---
### <font color="FF5151">51. 回溯</font>
#### [題目: N-Queens](https://leetcode.com/problems/n-queens/)
皇后可以攻擊同一行、同一列、同一對角線上的位置
* 對角線索引計算:
主對角線(左上到右下):row - col + n - 1
副對角線(右上到左下):row + col
回溯遞歸:
逐列嘗試放置皇后
檢查當前位置是否安全
如果安全,放置皇后並遞歸處理下一列
如果到達最後一列,記錄解決方案
回溯時移除皇后
```cpp=
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> result;
vector<string> board(n, string(n, '.'));
//檢查攻擊
vector<bool> col(n,false);
vector<bool> diag1(2*n-1,false);//左上到右下
vector<bool> diag2(2*n-1,false);//右上到左下
backtrack(result, board, 0, n, col, diag1, diag2);
return result;
}
void backtrack(vector<vector<string>>& result, vector<string>& board,
int row, int n, vector<bool>& cols,
vector<bool>& diag1, vector<bool>& diag2){
if(row==n){
result.push_back(board);
return ;
}
// 嘗試在當前列的每一行放置皇后
for (int col = 0; col < n; col++) {
// 計算對角線索引
int d1 = row - col + n - 1; // 主對角線索引
int d2 = row + col; // 副對角線索引
// 檢查是否安全(沒有攻擊)
if (cols[col] || diag1[d1] || diag2[d2]) {
continue; // 這個位置不安全,嘗試下一個
}
// 放置皇后
board[row][col] = 'Q';
cols[col] = true;
diag1[d1] = true;
diag2[d2] = true;
// 遞歸處理下一列
backtrack(result, board, row + 1, n, cols, diag1, diag2);
// 回溯:移除皇后
board[row][col] = '.';
cols[col] = false;
diag1[d1] = false;
diag2[d2] = false;
}
}
};
```
---
### <font color="FF5151">52. 回溯</font>
#### [題目: N-Queens II](https://leetcode.com/problems/n-queens-ii/description/)
同上題,只是改成傳數量而已
```cpp=
class Solution {
public:
int totalNQueens(int n) {
vector<vector<string>> result;
vector<string> board(n, string(n, '.'));
//檢查攻擊
vector<bool> col(n,false);
vector<bool> diag1(2*n-1,false);//左上到右下
vector<bool> diag2(2*n-1,false);//右上到左下
backtrack(result, board, 0, n, col, diag1, diag2);
return result.size();
}
void backtrack(vector<vector<string>>& result, vector<string>& board,
int row, int n, vector<bool>& cols,
vector<bool>& diag1, vector<bool>& diag2){
if(row==n){
result.push_back(board);
return ;
}
// 嘗試在當前列的每一行放置皇后
for (int col = 0; col < n; col++) {
// 計算對角線索引
int d1 = row - col + n - 1; // 主對角線索引
int d2 = row + col; // 副對角線索引
// 檢查是否安全(沒有攻擊)
if (cols[col] || diag1[d1] || diag2[d2]) {
continue; // 這個位置不安全,嘗試下一個
}
// 放置皇后
board[row][col] = 'Q';
cols[col] = true;
diag1[d1] = true;
diag2[d2] = true;
// 遞歸處理下一列
backtrack(result, board, row + 1, n, cols, diag1, diag2);
// 回溯:移除皇后
board[row][col] = '.';
cols[col] = false;
diag1[d1] = false;
diag2[d2] = false;
}
}
};
```
---
### <font color="c1ab0c">53. dp</font>
#### [題目:Maximum Subarray](https://leetcode.com/problems/maximum-subarray/description/)
核心思想:
對於每個位置 i,我們決定是否要包含前面的子陣列:
如果 currentSum < 0,則從當前元素重新開始
否則,將當前元素加到現有的子陣列中
```cpp=
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int curSum=nums[0],ans=nums[0];
for(int i=1;i<nums.size();i++){
curSum=max(nums[i],curSum+nums[i]);
ans=max(ans,curSum);
}
return ans;
}
};
```
---
### <font color="c1ab0c">54. 順時鐘遍歷矩陣</font>
#### [題目:Spiral Matrix](https://leetcode.com/problems/spiral-matrix/description//)
從左到右遍歷上邊界,然後上邊界下移
從上到下遍歷右邊界,然後右邊界左移
從右到左遍歷下邊界,然後下邊界上移
從下到上遍歷左邊界,然後左邊界右移
重要細節:
在步驟3和4之前要檢查是否還有行/列需要遍歷
避免在奇數維度矩陣中重複遍歷
```cpp=
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> ans;
if (matrix.empty() || matrix[0].empty()) {
return ans;
}
int top=0,down=matrix.size()-1;
int left=0,right=matrix[0].size()-1;
while(top<=down&&left<=right){
//右
for(int j=left;j<=right;j++){
ans.push_back(matrix[top][j]);
}
top++;//上邊界下移
//下
for(int i=top;i<=down;i++){
ans.push_back(matrix[i][right]);
}
right--;
//右邊界左移
//左
if(top<=down){
for(int j=right;j>=left;j--){
ans.push_back(matrix[down][j]);
}
down--;//下邊界上移
}
//上
if(left<=right){
for(int i=down;i>=top;i--){
ans.push_back(matrix[i][left]);
}
left++;
//左邊界右移
}
}
return ans;
}
};
```
---
### <font color="c1ab0c">55. 是否能到達最後一格</font>
#### [題目:Jump Game](https://leetcode.com/problems/jump-game/description/)
```cpp=
class Solution {
public:
bool canJump(vector<int>& nums) {
int curReach=0;
for(int i=0;i<nums.size();i++){
if(curReach>=i) curReach=max(curReach,i+nums[i]);
}
if(curReach>=nums.size()-1) return true;
else return false;
}
};
```
---
### <font color="c1ab0c">56. 合併重疊區間</font>
#### [題目:Merge Intervals](https://leetcode.com/problems/merge-intervals/description/)
- 兩種情況
- 如果當前區間的結束點 < 新區間的起始點
說明兩個區間不重疊,可以把當前區間加入結果
然後開始處理新的區間
- 如果兩個區間重疊,就合併它們
左邊界不變(因為已經排序,新區間的起始點 ≥ 當前起始點)
右邊界取較大值
```cpp=
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), [](const vector<int>& a,
const vector<int>& b) {
return a[0] < b[0]; // 升序排列
});
vector<vector<int>> ans;
int curMin=intervals[0][0],curMax=intervals[0][1];
for(int i=1;i<intervals.size();i++){
//如果目前最大比intervals[i]的起始還小,就往下格存
if(curMax<intervals[i][0]){
ans.push_back({curMin,curMax});
curMin=intervals[i][0];
curMax=intervals[i][1];
}
//起始在目前範圍內,但結尾較大
if(curMax<intervals[i][1]){
curMax=intervals[i][1];
}
}
//最後一個
ans.push_back({curMin,curMax});
return ans;
}
};
```
---
### <font color="c1ab0c">57. 插入區間</font>
#### [題目:Insert Interval](https://leetcode.com/problems/insert-interval/description/)
分三階段處理
```cpp=
class Solution {
public:
vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>&
newInterval) {
vector<vector<int>> ans;
// 處理空數組的情況
if(intervals.size() == 0) {
ans.push_back(newInterval);
return ans;
}
int i=0;
// 第一階段:添加所有在 newInterval 之前且不重疊的區間
while(i < intervals.size() && intervals[i][1] < newInterval[0]) {
ans.push_back(intervals[i]);
i++;
}
// 第二階段:合併所有與 newInterval 重疊的區間
int mergeStart = newInterval[0];
int mergeEnd = newInterval[1];
while(i < intervals.size() && intervals[i][0] <= newInterval[1]) {
mergeStart = min(mergeStart, intervals[i][0]);
mergeEnd = max(mergeEnd, intervals[i][1]);
i++;
}
ans.push_back({mergeStart, mergeEnd});
// 第三階段:添加所有在 newInterval 之後且不重疊的區間
while(i < intervals.size()) {
ans.push_back(intervals[i]);
i++;
}
return ans;
}
};
```
---
### <font color="green">58. 判斷最後一個字長度</font>
#### [題目:Length of Last Word](https://leetcode.com/problems/length-of-last-word/description/)
從後面數回來,記得先跳過最後的0
```cpp=
class Solution {
public:
int lengthOfLastWord(string s) {
int cnt=0;
//跳過倒數一開始就遇到的0
int start=s.length()-1;
while(s[start]==' '){
start--;
}
for(int i=start;i>=0;i--){
if(s[i]==' ') break;
cnt++;
}
return cnt;
}
};
```
---
### <font color="c1ab0c">59. 逆時鐘擺矩陣</font>
#### [題目:Spiral Matrix II](https://leetcode.com/problems/spiral-matrix-ii/description/)
邏輯同54題,cnt是現在要放的值
```cpp=
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> ans(n,vector<int>(n,0));
int left=0,right=n-1;
int top=0,down=n-1;
int cnt=1;
while(left<=right && top<=down){
//右
for(int j=left;j<=right;j++){
ans[top][j]=cnt;
cnt++;
}
top++;
//下
for(int i=top;i<=down;i++){
ans[i][right]=cnt;
cnt++;
}
right--;
//左
if(top<=down){
for(int j=right;j>=left;j--){
ans[down][j]=cnt;
cnt++;
}
down--;
}
//上
if(left<=right){
for(int i=down;i>=top;i--){
ans[i][left]=cnt;
cnt++;
}
left++;
}
}
return ans;
}
};
```
---
### <font color="FF5151">60. 排列序列</font>
#### [題目: Permutation Sequence](https://leetcode.com/problems/permutation-sequence/description/)
分組思想:第一位數字確定後,剩下的位置有 (n-1)! 種排列
數學計算:通過除法確定當前位置選哪個數字,通過取餘更新剩餘的k值
~~~~
所有排列:123, 132, 213, 231, 312, 321
要找第3個:213 (n=3,k=3)
1. 第1位:k=2, 每組大小=2!, 索引=2/2=1, 選numbers[1]=2
2. 第2位:k=0, 每組大小=1!, 索引=0/1=0, 選numbers[0]=1
3. 第3位:k=0, 每組大小=0!, 索引=0/1=0, 選numbers[0]=3
結果:213
~~~~
```cpp=
class Solution {
public:
string getPermutation(int n, int k) {
// 預計算階乘值
vector<int> factorial(n);
factorial[0] = 1;
for (int i = 1; i < n; i++) {
factorial[i] = factorial[i-1] * i;
}
// 建立可用數字列表 [1, 2, 3, ..., n]
vector<int> numbers;
for (int i = 1; i <= n; i++) {
numbers.push_back(i);
}
string result = "";
k--; // 轉換為0-based index
// 逐位確定每個位置的數字
for (int i = 0; i < n; i++) {
// 計算當前位置應該選擇第幾個數字
int index = k / factorial[n-1-i];
// 將選中的數字加入結果
result += to_string(numbers[index]);
// 從可用數字中移除已選的數字
numbers.erase(numbers.begin() + index);
// 更新k值
k %= factorial[n-1-i];
}
return result;
}
};
```
---
### <font color="c1ab0c">61. 逆時鐘擺矩陣</font>
#### [題目:Rotate List](https://leetcode.com/problems/rotate-list/description/)
```cpp=
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
// 邊界情況處理
if (!head || !head->next || k == 0) {
return head;
}
// 1. 計算鏈表長度並找到尾節點
ListNode* tail = head;
int length = 1;
while (tail->next) {
tail = tail->next;
length++;
}
// 2. 計算實際需要旋轉的步數
k = k % length;
if (k == 0) {
return head; // 不需要旋轉
}
// 3. 找到新的尾節點(從頭開始第 length-k 個節點)
ListNode* newTail = head;
for (int i = 0; i < length - k - 1; i++) {
newTail = newTail->next;
}
// 4. 新的頭節點是新尾節點的下一個
ListNode* newHead = newTail->next;
// 5. 斷開新尾節點的連接
newTail->next = nullptr;
// 6. 將原來的尾節點連接到原來的頭節點
tail->next = head;
return newHead;
}
};
```
---
### <font color="c1ab0c">62. dp</font>
#### [題目:Unique Paths](https://leetcode.com/problems/unique-paths/description/)
邊界只能直走因此都是1,其他位置都只可能從上方或左方到達(=兩者和)
```cpp=
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m, vector<int>(n, 1));
for(int i = 1; i < m; i++) {
for(int j = 1; j < n; j++) {
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
};
```
---
### <font color="c1ab0c">63. dp</font>
#### [題目:Unique Paths II](https://leetcode.com/problems/unique-paths-ii/description/)
```cpp=
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size();
int n = obstacleGrid[0].size();
// 特殊情況:起點或終點是障礙物
if(obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1) {
return 0;
}
vector<vector<int>> dp(m, vector<int>(n, 0));
// === 邊界處理 ===
// 第一列:只要遇到障礙物,後面都無法到達
dp[0][0] = 1;
for(int j = 1; j < n; j++) {
if(obstacleGrid[0][j] == 1) {
dp[0][j] = 0; // 障礙物,無法到達
} else {
dp[0][j] = dp[0][j-1]; // 繼承前一個位置的值
}
}
// 第一行:只要遇到障礙物,下面都無法到達
for(int i = 1; i < m; i++) {
if(obstacleGrid[i][0] == 1) {
dp[i][0] = 0; // 障礙物,無法到達
} else {
dp[i][0] = dp[i-1][0]; // 繼承上一個位置的值
}
}
// === 狀態轉移 ===
for(int i = 1; i < m; i++) {
for(int j = 1; j < n; j++) {
if(obstacleGrid[i][j] == 1) {
dp[i][j] = 0; // 障礙物,路徑數為 0
} else {
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
return dp[m-1][n-1];
}
};
```
---
### <font color="c1ab0c">64. dp</font>
#### [題目:Minimum Path Sum](https://leetcode.com/problems/minimum-path-sum/description/)
```cpp=
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
// === 邊界處理 ===
// 第一列
dp[0][0] = grid[0][0];
for(int j = 1; j < n; j++) {
dp[0][j]=dp[0][j-1]+grid[0][j];
}
// 第一行
for(int i = 1; i < m; i++) {
dp[i][0]=dp[i-1][0]+grid[i][0];
}
// === 狀態轉移 ===
for(int i = 1; i < m; i++) {
for(int j = 1; j < n; j++) {
int prevMin=min(dp[i-1][j],dp[i][j-1]);
dp[i][j]=prevMin+grid[i][j];
}
}
return dp[m-1][n-1];
}
};
```
---
### <font color="FF5151">65. 狀態機</font>
#### [題目: Valid Number](https://leetcode.com/problems/valid-number/description/)
```cpp=
class Solution {
public:
bool isNumber(string s) {
enum State {
START, // 開始狀態
SIGN, // 遇到正負號
DIGIT, // 遇到數字
DOT, // 遇到小數點
DIGIT_AFTER_DOT, // 小數點後的數字
EXP, // 遇到指數符號
EXP_SIGN, // 指數後的正負號
EXP_DIGIT // 指數後的數字
};
State state = START;
for (char c : s) {
switch (state) {
case START:
if (c == '+' || c == '-') {
state = SIGN;
} else if (isdigit(c)) {
state = DIGIT;
} else if (c == '.') {
state = DOT;
} else {
return false;
}
break;
case SIGN:
if (isdigit(c)) {
state = DIGIT;
} else if (c == '.') {
state = DOT;
} else {
return false;
}
break;
case DIGIT:
if (isdigit(c)) {
state = DIGIT;
} else if (c == '.') {
state = DIGIT_AFTER_DOT;
} else if (c == 'e' || c == 'E') {
state = EXP;
} else {
return false;
}
break;
case DOT:
if (isdigit(c)) {
state = DIGIT_AFTER_DOT;
} else {
return false;
}
break;
case DIGIT_AFTER_DOT:
if (isdigit(c)) {
state = DIGIT_AFTER_DOT;
} else if (c == 'e' || c == 'E') {
state = EXP;
} else {
return false;
}
break;
case EXP:
if (c == '+' || c == '-') {
state = EXP_SIGN;
} else if (isdigit(c)) {
state = EXP_DIGIT;
} else {
return false;
}
break;
case EXP_SIGN:
if (isdigit(c)) {
state = EXP_DIGIT;
} else {
return false;
}
break;
case EXP_DIGIT:
if (isdigit(c)) {
state = EXP_DIGIT;
} else {
return false;
}
break;
}
}
// 檢查最終狀態是否為有效狀態
return state == DIGIT || state == DIGIT_AFTER_DOT || state == EXP_DIGIT;
}
};
```
---
### <font color="green">66.手動加法</font>
#### [題目:Add Binary](https://leetcode.com/problems/add-binary/)
從最後一位往前處理進位
```cpp=
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
for(int i=digits.size()-1;i>=0;i--){
// 如果當前位小於 9,直接加 1 並返回
if(digits[i]!=9){
digits[i]++;
return digits;
}else{
// 如果當前位是 9,設為 0 並繼續處理進位
digits[i]=0;
}
}
// 如果所有位都是 9,需要在前面加一個 1
digits.insert(digits.begin(),1);
return digits;
}
};
```
---
### <font color="green">67.二進制加法</font>
#### [題目:Add Binary](https://leetcode.com/problems/add-binary/description/)
從最後一位往前處理進位
```cpp=
class Solution {
public:
string addBinary(string a, string b) {
int i=a.length()-1;
int j=b.length()-1;
string ans="";
int carry=0;
while(i>=0 || j>=0 || carry){
int sum=carry;
// 加上 a 的當前位
if (i >= 0) {
sum += a[i] - '0';
i--;
}
// 加上 b 的當前位
if (j >= 0) {
sum += b[j] - '0';
j--;
}
// 計算當前位的結果和進位
ans = char(sum % 2 + '0') + ans;
carry = sum / 2;
}
return ans;
}
};
```
---
### <font color="FF5151">68. 文本格式化</font>
~~屎題~~
#### [題目: Text Justification](https://leetcode.com/problems/text-justification/description/)
```cpp=
class Solution {
public:
vector<string> fullJustify(vector<string>& words, int maxWidth) {
vector<string> result;
int i = 0;
while (i < words.size()) {
// 找到當前行能容納的單詞
vector<string> currentLine;
int totalLength = 0;
while (i < words.size()) {
// 檢查是否能加入下一個單詞
int neededLength = totalLength + words[i].length() +
currentLine.size();
if (neededLength <= maxWidth) {
currentLine.push_back(words[i]);
totalLength += words[i].length();
i++;
} else {
break;
}
}
// 格式化當前行
bool isLastLine = (i == words.size());
string line = formatLine(currentLine, totalLength, maxWidth,
isLastLine);
result.push_back(line);
}
return result;
}
private:
string formatLine(vector<string>& words, int totalLength, int maxWidth,
bool isLastLine) {
if (words.size() == 1 || isLastLine) {
// 單個單詞或最後一行:左對齊
return leftJustify(words, maxWidth);
} else {
// 多個單詞:完全對齊
return fullJustifyLine(words, totalLength, maxWidth);
}
}
// 左對齊:單詞間只有一個空格,末尾補空格
string leftJustify(vector<string>& words, int maxWidth) {
string result = "";
for (int i = 0; i < words.size(); i++) {
result += words[i];
if (i < words.size() - 1) {
result += " ";
}
}
// 末尾補空格
while (result.length() < maxWidth) {
result += " ";
}
return result;
}
// 完全對齊:均勻分配空格
string fullJustifyLine(vector<string>& words, int totalLength, int maxWidth){
int gaps = words.size() - 1; // 單詞間的空隙數
int totalSpaces = maxWidth - totalLength; // 需要分配的總空格數
int extraSpaces = totalSpaces % gaps; // 需要額外分配的空格數
int avgSpaces = totalSpaces / gaps; // 每個空隙的平均空格數
string result = "";
for (int i = 0; i < words.size(); i++) {
result += words[i];
if (i < words.size() - 1) { // 不是最後一個單詞
// 計算當前空隙應該放多少空格
int spacesForThisGap = avgSpaces;
if (extraSpaces > 0) {
spacesForThisGap++; // 左邊的空隙多分配一個空格
extraSpaces--;
}
// 添加空格
for (int j = 0; j < spacesForThisGap; j++) {
result += " ";
}
}
}
return result;
}
};
```
---
### <font color="green">69. 開根號</font>
#### [題目:Sqrt(x)](https://leetcode.com/problems/sqrtx/description/)
```cpp=
class Solution {
public:
int mySqrt(int x) {
if (x == 0) return 0;
int left = 1, right = x;
int result = 0;
while (left <= right) {
int mid = left + (right - left) / 2;
//本來是mid * mid <= x
// 使用除法避免溢位:mid <= x / mid
if (mid <= x / mid) {
result = mid; // 記錄可能的答案
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
};
```
---
### <font color="green">70. 爬樓梯</font>
#### [題目:Climbing Stairs](https://leetcode.com/problems/climbing-stairs/description/)
費氏數列的概念
```cpp=
class Solution {
public:
int climbStairs(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
vector<int> memo(n+1, 0);
memo[1] = 1;
memo[2] = 2;
for(int i = 3; i <= n; i++) {
memo[i] = memo[i-1] + memo[i-2];
}
return memo[n];
}
};
```
---
### <font color="c1ab0c">71. stack</font>
#### [題目:Simplify Path](https://leetcode.com/problems/simplify-path/description/)
```cpp=
class Solution {
public:
string simplifyPath(string path) {
vector<string> stack;
string current = "";
// 在路徑末尾加一個'/',方便處理最後一個目錄
path += '/';
for (int i = 0; i < path.length(); i++) {
if (path[i] == '/') {
if (current == "..") {
// 返回上一級目錄
if (!stack.empty()) {
stack.pop_back();
}
} else if (current != "" && current != ".") {
// 普通目錄名,加入堆疊
stack.push_back(current);
}
current = ""; // 重置當前目錄名
} else {
current += path[i];
}
}
// 構建結果
string result = "";
for (const string& dir : stack) {
result += "/" + dir;
}
return result.empty() ? "/" : result;
}
};
```
---
### <font color="c1ab0c">72. dp</font>
#### [題目:Edit Distance](https://leetcode.com/problems/edit-distance/description/)
```cpp=
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.length();
int n = word2.length();
//dp[i][j]表示word1[0...i-1]變成word2[0...j-1]最少步驟數
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for(int i=0;i<=m;i++){
dp[i][0]=i; //變成空字串需刪除i次
}
for(int j=0;j<=n;j++){
dp[0][j]=j; //插入j次
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(word1[i-1]==word2[j-1]){
dp[i][j]=dp[i-1][j-1];//不需操作
}else {
// 字符不同,選擇最小的操作數
dp[i][j] = 1 + min({
dp[i-1][j], // 刪除 word1[i-1]
dp[i][j-1], // 插入 word2[j-1]
dp[i-1][j-1] // 替換 word1[i-1] 為 word2[j-1]
});
}
}
}
return dp[m][n];
}
};
```
---
### <font color="c1ab0c">73. 矩陣</font>
#### [題目:Set Matrix Zeroes](https://leetcode.com/problems/set-matrix-zeroes/)
用第一行和第一列紀錄是否那行/列變0
需額外處理第一行和第一列本身
```cpp=
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m=matrix.size();
int n=matrix[0].size();
// 用第一行和第一列作為標記
// 但需要額外變量記錄第一行和第一列本身是否有0
bool firstRowZero = false;
bool firstColZero = false;
//檢查第一行有沒有0
for(int i=0;i<m;i++){
if(matrix[i][0]==0){
firstColZero=true;
}
}
//檢查第一列有沒有0
for(int j=0;j<n;j++){
if(matrix[0][j]==0){
firstRowZero=true;
}
}
// 用第一行和第一列標記其他位置的0
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(matrix[i][j]==0){
matrix[i][0]=0;
matrix[0][j]=0;
}
}
}
//根據標記設置0(從第二行第二列開始)
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(matrix[0][j]==0||matrix[i][0]==0){
matrix[i][j]=0;
}
}
}
//處理第一行
for(int i=0;i<m;i++){
if(firstColZero){
matrix[i][0]=0;
}
}
//處理第一列
for(int j=0;j<n;j++){
if(firstRowZero){
matrix[0][j]=0;
}
}
return;
}
};
```
---
### <font color="c1ab0c">74. 矩陣</font>
#### [題目:Search a 2D Matrix](https://leetcode.com/problems/search-a-2d-matrix/)
先找可能在哪列,在遍歷這列看有沒有在裡面
```cpp=
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m=matrix.size();
int n=matrix[0].size();
int row=1;
//先找出在哪列
for(;row<m;row++){
if(matrix[row][0]>target) break;
}
//在matrix[row-1]這列
for(int j=0;j<n;j++){
if(matrix[row-1][j]==target) return true;
}
//不在陣列裡
return false;
}
};
```
---
### <font color="c1ab0c">75. 矩陣</font>
#### [題目:Search a 2D Matrix](https://leetcode.com/problems/search-a-2d-matrix/)
先統計在填入
```cpp=
class Solution {
public:
void sortColors(vector<int>& nums) {
int count0 = 0, count1 = 0, count2 = 0;
// 統計每種顏色的數量
for(int i = 0; i < nums.size(); i++){
if(nums[i] == 0) count0++;
else if(nums[i] == 1) count1++;
else count2++;
}
// 按順序填充數組
int idx = 0;
for(int i = 0; i < count0; i++) nums[idx++] = 0;
for(int i = 0; i < count1; i++) nums[idx++] = 1;
for(int i = 0; i < count2; i++) nums[idx++] = 2;
}
};
```
---
### <font color="FF5151">76. 雙指針</font>
#### [題目: Minimum Window Substring](https://leetcode.com/problems/minimum-window-substring/description/)
```cpp=
class Solution {
public:
string minWindow(string s, string t) {
if(s.empty()||t.empty()) return"";
//統計t的字
unordered_map<char,int> tCount;
for(char c:t){
tCount[c]++;
}
int required = tCount.size(); // t 中不重複字符的數量
int formed = 0; // 當前窗口中滿足頻率要求的不重複字符數量
// 窗口中字符的計數
unordered_map<char, int> windowCount;
// 記錄最小窗口的信息
int minLen = INT_MAX;
int minLeft = 0;
// 左右指針
int l = 0, r = 0;
while(r<s.length()){
// 將右指針的字符加入窗口
char c = s[r];
windowCount[c]++;
// 如果當前字符的頻率達到了 t 中的要求,則 formed 加 1
if (tCount.count(c) && windowCount[c] == tCount[c]) {
formed++;
}
// 嘗試收縮窗口,直到它不再滿足條件
while (l <= r && formed == required) {
// 更新最小窗口
if (r - l + 1 < minLen) {
minLen = r - l + 1;
minLeft = l;
}
// 將左指針的字符移出窗口
char leftChar = s[l];
windowCount[leftChar]--;
if (tCount.count(leftChar) &&
windowCount[leftChar] < tCount[leftChar]) {
formed--;
}
l++; // 左指針向右移動
}
r++;
}
return minLen == INT_MAX ? "" : s.substr(minLeft, minLen);
}
};
```
---
### <font color="c1ab0c">77. 回溯</font>
#### [題目:Combination](https://leetcode.com/problems/combinations/)
回溯法(Backtracking)
- 核心思想:
- 使用遞歸的方式逐步構建組合
每次選擇一個數字,然後遞歸處理剩餘的選擇
當組合大小達到 k 時,將其加入結果
```cpp=
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> ans;
vector<int> cur;
backtrack(ans,cur,n,k,1);
return ans;
}
void backtrack(vector<vector<int>> &ans,vector<int> &cur,int n,int k,int start){
// 基本情況:如果當前組合的大小等於 k,加入結果
if(cur.size()==k){
ans.push_back(cur);
return;
}
// 從 start 開始遍歷到 n
for(int i=start;i<=n;i++){
// 選擇當前數字
cur.push_back(i);
// 遞歸處理下一個位置,注意 start 變為 i + 1
backtrack(ans,cur,n,k,i+1);
// 回溯:移除當前數字
cur.pop_back();
}
}
};
```
---
### <font color="c1ab0c">78. 回溯</font>
#### [題目:Subsets](https://leetcode.com/problems/subsets/description/)
對於每個元素,我們有兩種選擇:包含或不包含
```cpp=
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> ans;
vector<int> cur;
backtrack(nums,ans,cur,0);
return ans;
}
void backtrack(vector<int>& nums,vector<vector<int>> &ans,
vector<int> &cur,int start){
ans.push_back(cur);
for(int i=start;i<nums.size();i++){
cur.push_back(nums[i]);
backtrack(nums,ans,cur,i+1);
cur.pop_back();
}
}
};
```
---
### <font color="c1ab0c">79. 回溯</font>
#### [題目:Subsets](https://leetcode.com/problems/subsets/description/)
每個位子都試試看當開頭往四邊找
```cpp=
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
if (board.empty() || board[0].empty() || word.empty()) {
return false;
}
for(int i=0;i<board.size();i++){
for(int j=0;j<board[0].size();j++){
if(cal(board,word,i,j,0)) return true;
}
}
return false;
}
bool cal(vector<vector<char>>& board, string word,int i,int j,int index){
// 先檢查邊界
if (i < 0 || i >= board.size() || j < 0 || j >= board[0].size()) {
return false;
}
// 檢查當前字符是否匹配
if (board[i][j] != word[index]) {
return false;
}
// 終止條件
if (index == word.length() - 1) {
return true;
}
// 防止重複使用同一個格子
char temp = board[i][j];
board[i][j] = '#'; // 標記已使用
// 四個方向搜索
bool found = cal(board, word, i + 1, j, index + 1) || // 向下
cal(board, word, i - 1, j, index + 1) || // 向上
cal(board, word, i, j + 1, index + 1) || // 向右
cal(board, word, i, j - 1, index + 1); // 向左
// 回溯
board[i][j] = temp;
return found;
}
};
```
---
### <font color="c1ab0c">80. 雙指針</font>
#### [題目:Remove Duplicates from Sorted Array II](https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/description/)
因為可以重複兩次,所以從第三格開始檢查
```cpp=
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.size() <= 2) {
return nums.size();
}
int ans=2; //結果組的目前位置,前兩個位置一定有
for(int i=2;i<nums.size();i++){
//檢查是否重複超過兩次
if(nums[i]!=nums[ans-2]){
nums[ans]=nums[i];
ans++;
}
}
return ans;
}
};
```
---
### <font color="c1ab0c">81. 二分搜</font>
#### [題目:Search in Rotated Sorted Array II](https://leetcode.com/problems/search-in-rotated-sorted-array-ii/description/)
```cpp=
class Solution {
public:
bool search(vector<int>& nums, int target) {
sort(nums.begin(),nums.end());
int left=0,right=nums.size()-1;
while(left<=right){
int mid = left + (right - left) / 2;
if(nums[mid]==target) return true;
else if(nums[mid]>target) right=mid-1;
else left=mid+1;
}
return false;
}
};
```
---