leetcode熱題100(持續更新中)


一、兩數之和

難度:簡單
題目:
給定一個整數數組 nums 和一個整數目標值 target,請你在該數組中找出 和為目標值 target  的那 兩個 整數,並返回它們的數組下標。
你可以假設每種輸入只會對應一個答案。但是,數組中同一個元素在答案里不能重復出現。
你可以按任意順序返回答案。
https://leetcode-cn.com/problems/two-sum/

我的題解:

點擊查看代碼
//時間復雜度為O(N^2)
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    vector<int> a;
    int i,j;
    int n=nums.size();
    for(i=0;i<n;i++){
        for(j=1;j<n;j++){//優化:for(j=i+1;j<n;j++){ //排除了i==j的情況,減少遍歷次數
            if(nums[i]+nums[j]==target&&i!=j){
                a.push_back(i);
                a.push_back(j);
                return a;
            }
    }
    }
    return a;
    }
};
時間復雜度更小的官方題解:
點擊查看代碼
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hashtable;
        for (int i = 0; i < nums.size(); ++i) {
            auto it = hashtable.find(target - nums[i]);//find()返回一個指向target - nums[i]的迭代器
            if (it != hashtable.end()) {
                return {it->second, i};//返回特定鍵所對的值
            }
            hashtable[nums[i]] = i;//使用[ ]進行單個插入,括號內為特定鍵,若已存在鍵值,則賦值修改,若無則插入。
        }
        return {};
    }
};
總結: 1.break只能跳出一層循環。

2.使用哈希表,可以將查找的時間復雜度降低到從O(N)降低到O(1)。

3.C++中有關Map的操作。
map: #include < map >
unordered_map: #include < unordered_map >
map: map內部實現了一個紅黑樹(紅黑樹是非嚴格平衡二叉搜索樹,而AVL是嚴格平衡二叉搜索樹),紅黑樹具有自動排序的功能,因此map內部的所有元素都是有序的,紅黑樹的每一個節點都代表着map的一個元素。因此,對於map進行的查找,刪除,添加等一系列的操作都相當於是對紅黑樹進行的操作。map中的元素是按照二叉搜索樹(又名二叉查找樹、二叉排序樹,特點就是左子樹上所有節點的鍵值都小於根節點的鍵值,右子樹所有節點的鍵值都大於根節點的鍵值)存儲的,使用中序遍歷可將鍵值按照從小到大遍歷出來。
unordered_map: unordered_map內部實現了一個哈希表(也叫散列表,通過把關鍵碼值映射到Hash表中一個位置來訪問記錄,查找的時間復雜度可達到O(1),其在海量數據處理中有着廣泛應用)。因此,其元素的排列順序是無序的。

C++ Map常見用法說明:

點擊查看代碼
#include <iostream>  
#include <unordered_map>  
#include <map>
#include <string>  
using namespace std;  
int main()  
{  
	//注意:C++11才開始支持括號初始化
    unordered_map<int, string> myMap={{ 5, "張大" },{ 6, "李五" }};//使用{}賦值
    myMap[2] = "李四";  //使用[ ]進行單個插入,若已存在鍵值2,則賦值修改,若無則插入。
    myMap.insert(pair<int, string>(3, "陳二"));//使用insert和pair插入
  
	//遍歷輸出+迭代器的使用
    auto iter = myMap.begin();//auto自動識別為迭代器類型unordered_map<int,string>::iterator
    while (iter!= myMap.end())
    {  
        cout << iter->first << "," << iter->second << endl;  
        ++iter;  
    }  
	
	//查找元素並輸出+迭代器的使用
    auto iterator = myMap.find(2);//find()返回一個指向2的迭代器
    if (iterator != myMap.end())
	    cout << endl<< iterator->first << "," << iterator->second << endl;  
    system("pause");  
    return 0;  
}  

二、合並兩個有序鏈表

難度:簡單
題目:將兩個升序鏈表合並為一個新的 升序 鏈表並返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。
https://leetcode-cn.com/problems/merge-two-sorted-lists/
遞歸解法:
之所以適合用到遞歸是因為題目的思想是找到最小的節點,連接次小的節點;這個不斷尋找最小值的過程適合用遞歸去實現。

點擊查看代碼
/**
 * 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* l1, ListNode* l2) {
if(l1==nullptr){
    return l2;
}
else if(l2==nullptr){
    return l1;
}
else if(l1->val<l2->val){
    l1->next=mergeTwoLists(l1->next,l2);
    return l1;
}
else{
    l2->next=mergeTwoLists(l2->next,l1);
    return l2;
}

    }
};

迭代解法:
思路
使用 dummy->next 來保存需要返回的頭節點。
判斷 l1 和 l2 哪個更小,就把這個節點接到下一個。
使用指向指針的指針 pp 用來存儲更小的一邊的指針。
在幫助 dummy 連接之后,還可以控制更小的 l1 或 l2 向后移動。
直到有一邊為 nullptr ,即可將另一邊剩余的都接上。

點擊查看代碼
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(0);
        ListNode* cur = dummy;
        while (l1 != nullptr && l2 != nullptr) {
            ListNode** pp = (l1->val < l2->val) ? &l1 : &l2;
            cur->next = *pp;
            cur = cur->next;
            *pp = (*pp)->next;
        }
        cur->next = (l1 == nullptr) ? l2 : l1;

        ListNode* ans = dummy->next;
        delete dummy;
        return ans;
    }

總結:注意判空,遞歸還是很不熟悉,鏈表指針也很不熟悉。

三、整數反轉

難度:簡單
題目:你一個 32 位的有符號整數 x ,返回將 x 中的數字部分反轉后的結果。如果反轉后整數超過 32 位的有符號整數的范圍 [−231,  231 − 1] ,就返回 0。
https://leetcode-cn.com/problems/reverse-integer/
我的思路:將整數轉換為字符串,再將字符串反轉。
更好的解題思路:
每次取末尾數字:x%10
取完后將x去掉末尾數字:x/10

點擊查看代碼
class Solution {
public:
    int reverse(int x) {
        int res = 0;
        while(x!=0) {
            //每次取末尾數字
            int tmp = x%10;
            //判斷是否 大於 最大32位整數
            if (res>214748364 || (res==214748364 && tmp>7)) {
                return 0;
            }
            //判斷是否 小於 最小32位整數
            if (res<-214748364 || (res==-214748364 && tmp<-8)) {
                return 0;
            }
            res = res*10 + tmp;
            x /= 10;
        }
        return res;
    }

};

四、有效的括號

難度:簡單
題目:給你一個 32 位的有符號整數 x ,返回將 x 中的數字部分反轉后的結果。如果反轉后整數超過 32 位的有符號整數的范圍 [−231,  231 − 1] ,就返回 0。
用到主要的方法:棧,map
思路:
1.若字符數串為單數可直接判斷是錯誤的。
2.括號匹配的查找:運用map形成鍵值對;
3.將字符串按單個字符進行遍歷 for (char ch: s)
4.建立棧: stack<棧元素類型> stk; 若為左括號則壓棧,若為右括號,棧空則判斷錯;棧不空,取棧頂元素看是否與當前字符對應的另一半括號匹配;若匹配,棧頂元素出棧;
5.若棧為空,表達式正確。

點擊查看代碼
class Solution {
public:
    bool isValid(string s) {
        int n = s.size();
        if (n % 2 == 1) {
            return false;
        }

        unordered_map<char, char> pairs = {
            {')', '('},
            {']', '['},
            {'}', '{'}
        };
        stack<char> stk;
        for (char ch: s) {
            if (pairs.count(ch)) {
                if (stk.empty() || stk.top() != pairs[ch]) {// pairs[ ]:用於通過鍵查找對應值
                    return false;
                }
                stk.pop();
            }
            else {
                stk.push(ch);
            }
        }
        return stk.empty();
    }
};

四、爬樓梯

難度:簡單
題目:假設你正在爬樓梯。需要 n 階你才能到達樓頂。每次你可以爬 1 或 2 個台階。你有多少種不同的方法可以爬到樓頂呢?注意:給定 n 是一個正整數。
方法:動態規划
我的題解:

點擊查看代碼
class Solution {
public:
    int climbStairs(int n) {
    int f[n+1];
    f[0]=1;
    f[1]=2;
    for(int i =2;i<n;i++){
        f[i]=f[i-1]+f[i-2];
    }
return f[n-1];
    }
};

五、最大子序和

難度:簡單
題目:給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。
方法:動態規划
難點在於轉移方程的構建:f(i)表示以第i個數結尾的最大子序和;f(i)=max{f(i-1)+nums[i],nums[i]};
我的題解:

點擊查看代碼
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n=nums.size();
int f[n];
f[0]=nums[0];
for(int i=1;i<n;i++){
f[i]=max(f[i-1]+nums[i],nums[i]);
}
int answer=-2147483648;
for(int i=0;i<n;i++){
answer=max(f[i],answer);
}
return answer;
    }
};

六、二叉樹的中序遍歷

難度:簡單
題目:給定一個二叉樹的根節點 root ,返回它的中序遍歷。
方法:遞歸

點擊查看代碼
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
 void inorder(TreeNode* root,vector<int> &answer){
         if(!root){return; }
         inorder(root->left, answer);
        answer.push_back(root->val);
        inorder(root->right, answer);
 }
    vector<int> inorderTraversal(TreeNode* root) {
          vector<int> answer;
          inorder(root,answer);
          return answer;   
    }
    
};

總結:1.這是一個簡單基礎的算法,必須掌握背誦。
2.C++中vector<int>& nums和vector<int> nums的區別:
當vector當作形參輸入到函數時,有兩種方法:
vector & a;(這種形式只能出現在形參中)
vector a;
帶&表示傳入函數的是vector的引用(即物理位置),函數內部對vector改動,vector就會改變;
不帶&表示傳入的是vector的復制品(開辟了另一塊位置),函數內部對其改動,不會影響原本的vector;

七、盛水最多的容器

難度:中等
標簽:Array
題目:Given n non-negative integers a1, a2, ..., an , where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of the line i is at (i, ai) and (i, 0). Find two lines, which, together with the x-axis forms a container, such that the container contains the most water.
Notice that you may not slant the container.
Example 1:

Input: height = [1,8,6,2,5,4,8,3,7]
Output: 49
Explanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.
https://leetcode.com/problems/container-with-most-water/
方法:雙指針
我的題解:兩層循環(超時了)

點擊查看代碼
class Solution {
public:
    int maxArea(vector<int>& height) {
        int max_area=0;
        int i,j;
        int n=height.size();
        int longth=0;
        for(i=0;i<n;i++){
            for(j=i+1;j<n;j++){
            longth=min(height[i],height[j]);
                if((j-i)*longth>max_area){
                    max_area=(j-i)*longth;
                }
        }
        }
        return max_area;
    }
}

官方題解:

點擊查看代碼
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);//height[l], height[r]:最開始兩個指針分別指向數組的兩頭
            ans = max(ans, area);//取每次以雙指針為左右邊界(也就是「數組」的左右邊界)計算出的容量中的最大值
            if (height[l] <= height[r]) {//若左指針的值比右指針小,將左指針向右移動;若右指針的值比左指針大,將右指針向左移動
                ++l;
            }
            else {
                --r;
            }
        }
        return ans;
    }
};

時間復雜度:O(N)O(N),雙指針總計最多遍歷整個數組一次。
空間復雜度:O(1)O(1),只需要額外的常數級別的空間。

總結:這道題最優的做法是使用「雙指針」,但是怎么能看出用雙指針最優呢???
雙指針代表的是 可以作為容器邊界的所有位置的范圍。在一開始,雙指針指向數組的左右邊界,表示 數組中所有的位置都可以作為容器的邊界,因為我們還沒有進行過任何嘗試。在這之后,我們每次將 對應的數字較小的那個指針 往 另一個指針 的方向移動一個位置,就表示我們認為 這個指針不可能再作為容器的邊界了。

七、三數之和

難度:中等
標簽:Array
題目:Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.Notice that the solution set must not contain duplicate triplets.
https://leetcode.com/problems/3sum/
方法:雙指針
當我們需要枚舉數組中的兩個元素時,如果我們發現隨着第一個元素的遞增,第二個元素是遞減的,那么就可以使用雙指針的方法,將枚舉的時間復雜度從O(N^2)減少至O(N)。

解題思路:
1.除去重復:因為不能重復,所以將數組排序,使得枚舉出的組合為(a,b,c)(a>=b>=c),保證了只有 (a, b, c)(a,b,c) 這個順序會被枚舉到,而 (b, a, c)(b,a,c)、(c, b, a)(c,b,a) 等等這些不會,這樣就減少了重復。
2.跳出三重循環的大框架:
如果我們固定了前兩重循環枚舉到的元素 a 和 b,那么只有唯一的 c 滿足 a+b+c=0。當第二重循環往后枚舉一個元素b'時,由於 b' > b ,那么滿足 a+b'+c'=0,a+b′+c'=0 的 c' 一定有 c' < c,即 c' 在數組中一定出現在c的左側。也就是說,我們可以從小到大枚舉b,同時從大到小枚舉c,即第二重循環和第三重循環實際上是並列的關系。(雙指針)有了這樣的發現,我們就可以保持第二重循環不變,而將第三重循環變成一個從數組最右端開始向左移動的指針。

點擊查看代碼
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> answer;
        int n=nums.size();
        int sum=0;
        int l=0;
        sort(nums.begin(), nums.end());
        //枚舉a
        for(int i=0;i<n;i++){
            //減少重復枚舉
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int k=n-1;
            int target = -nums[i];
            for (int j = i + 1; j < n; ++j) {
                // 需要和上一次枚舉的數不相同
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                // 需要保證 b 的指針在 c 的指針的左側
                while (j < k && nums[j] + nums[k] > target) {
                    --k;
                }
                // 如果指針重合,隨着 b 后續的增加
                // 就不會有滿足 a+b+c=0 並且 b<c 的 c 了,可以退出循環
                if (j == k) {
                    break;
                }
                if (nums[j] + nums[k] == target) {
                    answer.push_back({nums[i], nums[j], nums[k]});
                }
        }
        }
        return answer;
    }
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM