1. 程式人生 > 其它 >Hot 100(11~20)

Hot 100(11~20)

Hot 100(11~20)

目錄

11.有效的括號

給定一個只包括 '(',')','{','}','[',']' 的字串 s ,判斷字串是否有效。

有效字串需滿足:

左括號必須用相同型別的右括號閉合。
左括號必須以正確的順序閉合。

輔助棧

對於判斷這種左右匹配,可以考慮用棧和雜湊表來輔助。

思路就是遍歷字串中的每個字元,如果遍歷到雜湊表中的key(也就是右括號),並且如果此時的棧為空(棧中沒有與之對應的左括號)或者棧頂不是對應的左括號,則是不匹配的返回false,匹配的話就彈出棧頂。遍歷到左括號直接壓入棧即可。注意特殊情況,匹配肯定是成對匹配,如果字串長度不是偶數,則直接返回。

bool isValid(string s)
{
    if (string.size() % 2 != 0) return false;
    unordered_map<char, char> hash = {
        {')', '('},
        {']', '['},
        {'}', '{'}
    };
    stack<char> stk;
    
    for (auto ch : s)
    {
        if (hash.count(ch)) // count函式直接返回的是一個數值,如果存在那麼返回1,反之0
        {
            if (stk.empty() || stk.top() != hash[ch]) return false;
            stk.pop();
        }
        else stk.push(ch);
    }
    return stk.empty();
}

時間複雜度O(n),n是字串長度,空間複雜度是O(n+6),用棧儲存字串空間複雜度為n,6則是雜湊表


12.合併兩個連結串列

將兩個升序連結串列合併為一個新的 升序 連結串列並返回。新連結串列是通過拼接給定的兩個連結串列的所有節點組成的。

合併為一個新的連結串列,使用一個指標逐個遍歷比較,然後修改next。要新建一個頭節點,方便返回。

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{
    ListNode* head = new ListNode(-1);
    ListNode* pre = head;
    
    while (l1 != nullptr && l2 != nullptr)
    {
        if (l1->val > l2->val) 
        {
            pre->next = l2;
            l2 = l2->next;
        }
        else
        {
            pre->next = l1;
            l1 = l1->next;
        }
    }
    pre->next = l1 == nullptr ? l2 : l1;
    return head->next;
}

時間複雜度O(n+m),n和m是兩個連結串列的長度, 空間複雜度O(1)


13.括號生成

數字 n 代表生成括號的對數,請你設計一個函式,用於能夠生成所有可能的並且 有效的 括號組合。

看到這個類似全排列的問題,可以用暴力解題,題解思路是列出所有可能的組合,然後再判斷它是否有效。但是這種時間複雜度過高,一般不會考慮。大多數會使用深搜dfs來解題,第一位放什麼,然後往下遞迴,構建二叉樹。

dfs遞迴

關鍵點在於搜尋序列時的當前位,是選擇左括號還是右括號,並且需要生成有效的括號序列。

首先左括號數量不能大於n,當條件成立時新增左括號。其次當右邊括號小於n並且左邊括號大於右括號時,新增右括號。遞迴的結束條件是左右兩遍括號數量都等於n。

引數傳遞是值傳遞,值傳遞的好處就是下一層的結果不會對上一層造成任何影響,因此我們回溯到上一層時,程式會自動幫我們擦除當前層的選擇。

public:
    vector<string> res; //記錄答案 
    vector<string> generateParenthesis(int n) {
        dfs(n , 0 , 0, "");
        return res;
    }
    void dfs(int n ,int lc, int rc ,string str)
    {
        if( lc == n && rc == n) res.push_back(str);    //遞迴邊界
        else
        {
            if(lc < n) dfs(n, lc + 1, rc, str + "(");            //拼接左括號
            if(rc < n && lc > rc) dfs(n, lc, rc + 1, str + ")"); //拼接右括號
        }
    }

時間複雜度是$O(C_{2n}^n)$。空間複雜度$O(n)$,遞迴2n層,每層常數空間使用,所以漸進複雜為O(n)


15.下一個排列

整數陣列的一個 排列 就是將其所有成員以序列或線性順序排列。

例如,arr = [1,2,3] ,以下這些都可以視作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整數陣列的 下一個排列 是指其整數的下一個字典序更大的排列。更正式地,如果陣列的所有排列根據其字典順序從小到大排列在一個容器中,那麼陣列的 下一個排列 就是在這個有序容器中排在它後面的那個排列。如果不存在下一個更大的排列,那麼這個陣列必須重排為字典序最小的排列(即,其元素按升序排列)。

例如,arr = [1,2,3] 的下一個排列是 [1,3,2] 。
類似地,arr = [2,3,1] 的下一個排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一個排列是 [1,2,3] ,因為 [3,2,1] 不存在一個字典序更大的排列。
給你一個整數陣列 nums ,找出 nums 的下一個排列。

必須 原地 修改,只允許使用額外常數空間。

這題的題意是按照字典序找出下一個排列。下一個排列比當前排列要大,並且變大的幅度需要最小。如果更改前面的數字,那麼變大的幅度是最大的,所以進行修改後面的數字。

兩次掃描

第一次從後往前尋找第一個逆序。第二次掃描找出比第一個它大的位。

void nextPermutation(vector<int>& nums)
{
	int i = nums.size() - 2; // 為了找出後面的第一個逆序
	while (i >= 0 && nums[i] >= nums[i + 1]) i --; // 尋找逆序
    
    if (i >= 0) 
    {
        int j = nums.size() - 1; // 尋找第一個比i大的數字 
        while (nums[i] >= nums[j]) j --;
        swap(nums[i], nums[j]);
    }
    reverse(nums.begin() + i + 1, nums.end()); // 將i後面的數反轉
}

時間複雜度是$O(N)$,N是給定序列長度,最多需要兩次掃描,以及一次反轉。空間複雜度$O(1)$


17.搜尋旋轉排序陣列

整數陣列 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 。

二分查詢

最簡單的辦法就是開一個雜湊表記住值和下標,sort排序看看 nums[target] 是否等於 target ,如果不是則返回-1,是則從雜湊表中返回value。但是本題需要用$O(logn)$的方法求解,於是可以用二分的方法。將旋轉後的陣列一分為二,一部分是有序的,另一部分是部分有序的,

int search(vector<int>& nums, int target)
{
    int n = nums.size();
    if (!n) return -1; // 特判陣列為空或者只有一個數的情況
    if (n == 1) return nums[0] == target ? 0 : -1;
    
    int l = 0, r = n - 1;
    while (l <= r)
    {
        int mid = l + r >> 1;
        if (nums[mid] == target) return mid;
        
        if (nums[0] <= nums[mid]) // 說明這部分有序
        {				// 如果target在0到mid這個區間,更新右邊界
            if (nums[0] <= target && target < nums[mid]) r = mid - 1;
            else l = mid + 1;
        }
        else 
        {
            if (nums[mid] < target && target <= nums[n - 1]) l = mid + 1;
            else r = mid - 1;
        }
    }
    return -1; // 退出while迴圈說明沒有該target
}

時間複雜度$O(log n)$,空間複雜度$O(1)$


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

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

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

進階:

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

最容易讓人想到的解題方式就是直接遍歷,如果沒搜到target,直接返回{-1,-1},如果搜到了,則記下當前下標,直到遍歷比target大的數,返回下標組。但這種解題方式是O(n)的。

這題類似於ACWing上的數的範圍,因為給出的陣列是有序的,所以可以使用二分查詢來優化時間複雜度。

二分查詢

vector<int> searchRange(vector<int>& nums, int target)
{
    int n = nums.size();
    if (!n) return {-1, -1};
	int l = 0, r = n - 1;
    int left = 0;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (nums[mid] >= target) r = mid;
        else l = mid + 1;
    }
    
    if (nums[l] != target) return {-1, -1};
    else 
    {
        left = l;
        r = n - 1;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (nums[mid] <= target) l = mid;
            else r = mid - 1;
        }
        return {left, r};
    }
    return {-1, -1};
}

時間複雜度為$O(logn)$,空間複雜度為$O(1)$


19.組合總和

給你一個 無重複元素 的整數陣列 candidates 和一個目標整數 target ,找出 candidates 中可以使數字和為目標數 target 的 所有 不同組合 ,並以列表形式返回。你可以按 任意順序 返回這些組合。

candidates 中的 同一個 數字可以 無限制重複被選取 。如果至少一個數字的被選數量不同,則兩種組合是不同的。

對於給定的輸入,保證和為 target 的不同組合數少於 150 個。

對於這類尋找可行解的問題,都可以用搜索回溯的方法。從陣列第0位開始,每次選擇該位或不選擇該位,向下搜尋可形成一棵樹。

遞迴函式dfs需要維護三個引數,一是目標值target,每次選擇一位後target減去該位。二是當前的組合combine。三是下標dix,如果選擇當前位,則combine + 1,idx不變(因為可以有重複),如果不選擇該位,則idx + 1。

遞迴結束條件:下標等於candidates的size;

vector<vector<int>> combinationSum(vector<int>& candidates, int target)
{
	vector<vector<int>> ans; // combine里加起來為target則新增到ans裡
    vector<int> combine; // 當前組合
    dfs(candidates, target, ans, combine, 0); // 下標從0開始
    return ans;
}
		 // 給定的陣列               目標和              答案陣列               當前組合              下標
void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& combine, int idx)
{
    if (idx == candidates.size()) return;
    if (target == 0) 
    {
        ans.push_back(combine);
        return;
    }
    
    // 跳過當前位
    dfs(candidates, target, ans, combine, idx + 1);
    
    // 選擇當前位
    if (target - candidates[idx] >= 0)
    {
        combine.push_back(candidates[idx]);
        dfs(candidates, target - candidates[idx], ans, combine, idx);
        combine.pop_back(); // 恢復現場
    }
}

時間複雜度$O(S)$,取決於搜尋樹所有葉子節點的深度之和。空間複雜度$O(target)$最壞遞迴target層