1. 程式人生 > 其它 >力扣leetcode-演算法基礎21天刷題記錄①

力扣leetcode-演算法基礎21天刷題記錄①


力扣【leetcode】 演算法基礎21天刷題 記錄篇一


菜雞演算法刷題打卡!!


⭐二分查詢


34. 在排序陣列中查詢元素的第一個和最後一個位置


給定一個按照升序排列的整數陣列 nums,和一個目標值 target。找出給定目標值在陣列中的開始位置和結束位置。

如果陣列中不存在目標值 target,返回 [-1, -1]。

進階:

你可以設計並實現時間複雜度為 O(log n) 的演算法解決此問題嗎?


二分查詢


兩次二分查詢應用


class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int n = nums.size();
        int l = 0, r = n - 1;
        int index1 = n, index2 = n;
        // 獲取第一個跟目標數相等的數的下標
        while(l <= r){
            int mid = l + (r - l) / 2;
            if(nums[mid] >= target) {
                r = mid - 1;
                index1 = mid;
            }
            else{
                l = mid + 1;
            }
        }

        // 獲取最後一個跟目標數相等的數的下標
        l = 0, r = n - 1;
        while(l <= r){
            int mid = l + (r - l) / 2;
            if(nums[mid] > target) {
                r = mid - 1;
                index2 = mid;
            }
            else{
                l = mid + 1;
            }
        }
        index2--;
        if(index1 <= index2 && index2 < n && nums[index1] == target && nums[index2] == target){
            return vector<int>{index1, index2};
        }
        else {
            return vector<int>{-1, -1};
        }
    }
};

33. 搜尋旋轉排序陣列


整數陣列 nums 按升序排列,陣列中的值 互不相同 。

在傳遞給函式之前,nums 在預先未知的某個下標 k(0 <= k < nums.length)上進行了 旋轉,使陣列變為 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下標 從 0 開始 計數)。例如, [0,1,2,4,5,6,7] 在下標 3 處經旋轉後可能變為 [4,5,6,7,0,1,2] 。

給你 旋轉後 的陣列 nums 和一個整數 target ,如果 nums 中存在這個目標值 target ,則返回它的下標,否則返回 -1 。


// 旋轉排序陣列本質上就是兩段升序陣列,實際上,抽象講就是經歷一次的二分查詢
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size();
        int l = 0, r = n - 1;
        while(l < r) {
            // r獲取兩段升序的銜接
            if (nums[r] - nums[l] < 0){
                r--;
            }
            else {
                break;
            }
        }

        // 分情況二分查詢
        int i, j;
        if (target == nums[r]){
            return r;
        }
        // 注意等號!若無,則會忽略第一個元素是尋找數的情況 
        else if(target >= nums[0] && target < nums[r]){
            // target落在 0~r 間的升序陣列
            i = 0;
            j = r - 1;
        }
         // target落在 r~n-1 間的升序陣列
        else if (target < nums[0]){
            i = r + 1;
            j = n - 1;
        }
        else {
            return -1;
        }
    //    二分查詢
       while(i <= j) {
            int mid = i + (j - i) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            else if ( nums[mid] < target) {
                i = mid + 1;
            }
            else{
                j = mid - 1;
            }
        }
        return -1;
    }
};

74. 搜尋二維矩陣


編寫一個高效的演算法來判斷 m x n 矩陣中,是否存在一個目標值。該矩陣具有如下特性:

每行中的整數從左到右按升序排列。
每行的第一個整數大於前一行的最後一個整數。


class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int n = matrix[0].size(), m = matrix.size();
        int l = 0, r = m * n -1;
        while(l <= r){
            int mid = l + (r - l) / 2;
            if (matrix[mid / n][mid % n] == target) {
                return true;
            }
            else if (matrix[mid / n][mid % n] < target) {
                l = mid + 1;
            }
            else {
                r = mid - 1;
            }
        }
        return false;
    }
};

153. 尋找旋轉排序陣列中的最小值


已知一個長度為 n 的陣列,預先按照升序排列,經由 1 到 n 次 旋轉 後,得到輸入陣列。例如,原陣列 nums = [0,1,2,4,5,6,7] 在變化後可能得到:
若旋轉 4 次,則可以得到 [4,5,6,7,0,1,2]
若旋轉 7 次,則可以得到 [0,1,2,4,5,6,7]
注意,陣列 [a[0], a[1], a[2], ..., a[n-1]] 旋轉一次 的結果為陣列 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

給你一個元素值 互不相同 的陣列 nums ,它原來是一個升序排列的陣列,並按上述情形進行了多次旋轉。請你找出並返回陣列中的 最小元素 。


// 利用旋轉,產生兩段升序的陣列,則找到分界點,利用升序特點進行求解最小值
class Solution {
public:
    int findMin(vector<int>& nums) {
        int  n = nums.size();
        int l = 0, r = n - 1;
        while(l <= r) {
            if (nums[r] - nums[l] < 0) {
                r--;
            }
            else{
                break;
            }
        }
        // 若直接是升序陣列,則兩段升序陣列並不存在,可直接返回
        if (r != n-1) {
            return min(nums[0],nums[r + 1]);
        }
        else {
            return nums[0];
        }
    }
};

162. 尋找峰值


峰值元素是指其值大於左右相鄰值的元素。

給你一個輸入陣列 nums,找到峰值元素並返回其索引。陣列可能包含多個峰值,在這種情況下,返回 任何一個峰值 所在位置即可。

你可以假設 nums[-1] = nums[n] = -∞ 。


// 需要再考慮一種情況,即升序,無降,此時返回陣列最後一個位置
class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int n = nums.size();
        int i = 0;
        int index = -1;
        while(i < n-1){
            if (nums[i] < nums[i+1]) {
                i++;
            }
            else {
                index = i;
                break;
            }
        }
        if (i == n-1){
            index = n-1;
        }
        return index;
    }
};

⭐雙指標


82. 刪除排序連結串列中的重複元素 II


存在一個按升序排列的連結串列,給你這個連結串列的頭節點 head ,請你刪除連結串列中所有存在數字重複情況的節點,只保留原始連結串列中 沒有重複出現 的數字。

返回同樣按升序排列的結果連結串列。


/**
 * 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* deleteDuplicates(ListNode* head) {
        if(!head){
            return head;
        }
        ListNode* dumpy = new ListNode(0, head);
        ListNode* cur = dumpy;
        // 遍歷整個連結串列
        while(cur->next && cur->next->next) {
            // 找到重複的連結串列,進行迴圈替代
            if(cur->next->val == cur->next->next->val) {
                int x = cur->next->val;
                while(cur->next && cur->next->val == x){
                    cur->next = cur->next->next;
                }
            }
            else{
                cur = cur->next;
            }
        }
        return dumpy->next;
    }
};

15. 三數之和


給你一個包含 n 個整數的陣列 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有和為 0 且不重複的三元組。

注意:答案中不可以包含重複的三元組。


// 對陣列進行升序排序,k指標從頭遍歷.利用i j雙指標進行移動
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> sum;
        // 進行排序
        sort(nums.begin(), nums.end());
        for(int k = 0; k < nums.size(); ++k){
            // 正數,相加也不會等於0
            if(nums[k] > 0) {
                break;
            }
            // 相等
            if(k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            int i = k + 1, j = n - 1;
            while(i < j) {
                int ans = nums[k] + nums[i] + nums[j];
                // <0 說明負數這邊更大,因此移動左指標,使得和接近0
                if( ans < 0) {
                    while(i < j && nums[i] == nums[++i]);
                }
                else if (ans > 0) {
                    while(i < j && nums[j] == nums[--j]);
                }
                else{
                    sum.push_back(vector<int> {nums[k], nums[i], nums[j]});
                    while(i < j && nums[i] == nums[++i]);
                    while(i < j && nums[j] == nums[--j]);
                }
            }  
        }
        return sum;
    }
};

844. 比較含退格的字串


給定 S 和 T 兩個字串,當它們分別被輸入到空白的文字編輯器後,判斷二者是否相等,並返回結果。 # 代表退格字元。

注意:如果對空文字輸入退格字元,文字繼續為空。


class Solution {
public:
    bool backspaceCompare(string s, string t) {
        return backTo(s) == backTo(t);
    }

    string backTo(string str) {
        string s;
        for (char ch : str) {
            if(ch != '#') {
                // 把非#的字元插入字串
                s.push_back(ch);
            }
            else if (! s.empty()) {
                // 遇到#,將此時字串中的最後一個字元切除
                s.pop_back();
            }
        }
        return s;
    }
};

986. 區間列表的交集


給定兩個由一些 閉區間 組成的列表,firstList 和 secondList ,其中 firstList[i] = [starti, endi] 而 secondList[j] = [startj, endj] 。每個區間列表都是成對 不相交 的,並且 已經排序 。

返回這 兩個區間列表的交集 。

形式上,閉區間 [a, b](其中 a <= b)表示實數 x 的集合,而 a <= x <= b 。

兩個閉區間的 交集 是一組實數,要麼為空集,要麼為閉區間。例如,[1, 3] 和 [2, 4] 的交集為 [2, 3] 。


class Solution {
public:
    vector<vector<int>> intervalIntersection(vector<vector<int>>& firstList, vector<vector<int>>& secondList) {
        vector<vector<int>> ans;
        int i = 0, j = 0;
        while (i < firstList.size() && j < secondList.size()) {
            int low = max(firstList[i][0], secondList[j][0]);
            int high = min(firstList[i][1], secondList[j][1]);
            if (low <= high) {
                ans.push_back(vector<int> {low, high});
            }
            if (firstList[i][1] < secondList[j][1]) {
                i++;
            }
            else{
                j++;
            }
        }
        return ans;
    }
};

11. 盛最多水的容器


給你 n 個非負整數 a1,a2,...,an,每個數代表座標中的一個點 (i, ai) 。在座標內畫 n 條垂直線,垂直線 i 的兩個端點分別為 (i, ai) 和 (i, 0) 。找出其中的兩條線,使得它們與 x 軸共同構成的容器可以容納最多的水。

說明:你不能傾斜容器。


class Solution {
public:
    int maxArea(vector<int>& height) {
        int l = 0, r = height.size() - 1;
        int ans = 0;
        while (l < r) {
            int area = min(height[l], height[r]) * (r - l);
            ans = max(ans, area);
            if (height[l] <= height[r]) {
                ++l;
            }
            else {
                --r;
            }
        }
        return ans;
    }
};

⭐滑動視窗

438. 找到字串中所有字母異位詞

給定兩個字串 s 和 p,找到 s 中所有 p 的 異位詞 的子串,返回這些子串的起始索引。不考慮答案輸出的順序。

異位詞 指字母相同,但排列不同的字串。


// 類似567. 字串的排列。也就是說只需考慮字母出現的次數以及總長度即可
class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int n = p.length();
        vector<int> a(26,0);
        for(auto c : p){
            a[c-'a']++;
        }
        int l = 0, r = 0;
        vector<int> a1(a);
        vector<int> index;
        while(r < s.size()){
            a1[s[r] - 'a']--;
            while(a1[s[r] - 'a'] < 0){
                a1[s[l] - 'a']++;
                l++;
            }
            if(n == r - l + 1) {
                index.push_back(l);
            }
            r++;
        }
        return index;
    }
};

713. 乘積小於K的子陣列


給定一個正整數陣列 nums和整數 k

請找出該陣列內乘積小於 k 的連續的子陣列的個數。


簡單思路,就是從第一個數開始往後迭乘,超過k則跳出迴圈、

class Solution {
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) {
        int n = nums.size();
        int l = 0, r = 0, cnt;
        int sum = 0;
        while(l < n) {
            r = l;
            cnt = 1;
            while(r < n) {
                cnt *= nums[r++];
                if(cnt >= k){
                    break;
                }
                sum++;         
            }
            l++;
        }
        return sum;
    }
};

// 但是很遺憾,超出時間限制。於是我們換個思路來解決該難題。
// 之前我們一直是遍歷陣列進行迭乘,那麼我們是否可以只迭乘一次?
// 將右指標固定移動遍歷。利用左指標縮短左右指標間隔? 如下:


通過


class Solution {
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) {
        // 正整數乘積小於1,不存在直接返回。
        if(k <= 1)
            return 0;
        int l = 0, r = 0, n = nums.size();
        int sum = 0, cnt = 1;
        while(r < n) {
            // 右指標進行移動,一直迭乘
            cnt *= nums[r];
            while(cnt >= k) {
                // 左指標進行移動條件為 迭乘超過k
                cnt /= nums[l++];
            }
            // 左右指標間隔差即為小於k的連續子陣列的個數
            sum += r - l + 1;
            r++;
        }
        return sum;
    }
};

209. 長度最小的子陣列


給定一個含有 n 個正整數的陣列和一個正整數 target 。

找出該陣列中滿足其和 ≥ target 的長度最小的 連續子陣列 [numsl, numsl+1, ..., numsr-1, numsr] ,並返回其長度。如果不存在符合條件的子陣列,返回 0 。


跟上一題差不多


class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size();
        if(n == 0){
            return 0;
        }
        int mins = n;
        int l = 0, r = 0, len = 0;
        int sum = 0;
        bool flag = false;
        while(r < n){
            sum += nums[r];
            while(sum >= target) {
                len = r - l + 1;
                mins = min(mins, len);
                sum -= nums[l];
                l++;
                // 標記存在符合條件的連續子陣列
                flag = true;
            }
            r++;
        }
        if(!flag)
            mins = 0;
        return mins;
    }
};