LeetCodet探索——陣列與字串

okumiko發表於2020-11-05

給定一個整數型別的陣列 nums,請編寫一個能夠返回陣列 “中心索引” 的方法。

我們是這樣定義陣列 中心索引 的:陣列中心索引的左側所有元素相加的和等於右側所有元素相加的和。

如果陣列不存在中心索引,那麼我們應該返回 -1。如果陣列有多箇中心索引,那麼我們應該返回最靠近左邊的那一個。

示例 1:

輸入:
nums = [1, 7, 3, 6, 5, 6]
輸出:3
解釋:
索引 3 (nums[3] = 6) 的左側數之和 (1 + 7 + 3 = 11),與右側數之和 (5 + 6 = 11) 相等。
同時, 3 也是第一個符合要求的中心索引。

示例 2:

輸入:
nums = [1, 2, 3]
輸出:-1
解釋:
陣列中不存在滿足此條件的中心索引。

說明:

  • nums 的長度範圍為 [0, 10000]
  • 任何一個 nums[i] 將會是一個範圍在 [-1000, 1000]的整數。

方法:字首和
唯一要思考的地方是如何減少計算量,如果每一箇中心時都要左右全部計算sum一次會超時,觀察中間遊標i的移動,當我們知道元素總和與i左側元素的和時,右邊元素的和用減法就能得到,無需再次迴圈計算。

演算法:

  • S 是陣列的和,當索引 i 是中心索引時,位於 i 左邊陣列元素的和 leftsum 滿足 S - nums[i] - leftsum
  • 我們只需要判斷當前索引 i 是否滿足 leftsum==S-nums[i]-leftsum 並動態計算 leftsum 的值。
class Solution {
public:
    int pivotIndex(vector<int>& nums) {
        int S=accumulate(nums.begin(),nums.end(),0);
        for(int i=0,leftsum=0;i<nums.size();i++ ){
            if(leftsum==S-nums[i]-leftsum)return i;
            leftsum=sum+nums[i];
        }
        return -1;
    }
};

這個題比較奇怪,中心索引可以取到邊界,所以要先判斷再求和。因為leftsum可以沒有元素,但最多有nums.size()-1個元素相加。

複雜度分析

  • 時間複雜度:O(N)O(N),其中 NN 是 nums 的長度。
  • 空間複雜度:O(1)O(1),使用了 Sleftsum

accumulate函式

  • accumulate定義在#include<numeric>中,作用有兩個,一個是累加求和,另一個是自定義型別資料的處理。
  • accumulate帶有三個形參:頭兩個形參指定要累加的元素範圍,第三個形參則是累加的初值。
  • accumulate函式將它的一個內部變數設定為指定的初始值,然後在此初值上累加輸入範圍內所有元素的值。accumulate演算法返回累加的結果,其返回型別就是其第三個實參的型別。

搜尋插入位置
給定一個排序陣列和一個目標值,在陣列中找到目標值,並返回其索引。如果目標值不存在於陣列中,返回它將會被按順序插入的位置。你可以假設陣列中無重複元素。

示例 1:

輸入: [1,3,5,6], 5
輸出: 2

示例 2:

輸入: [1,3,5,6], 2
輸出: 1

示例 3:

輸入: [1,3,5,6], 7
輸出: 4

示例 4:

輸入: [1,3,5,6], 0
輸出: 0

思路2:在迴圈體內部排除元素( leetcode-cn.com/problems/search-in... 轉自@liweiwei1419)

  • while(left < right)這種寫法表示在迴圈體內部排除元素;
  • 退出迴圈的時候 leftright 重合,區間 [left, right] 只剩下 1 個元素,這個元素 有可能 就是我們要找的元素。

LeetCodet探索——陣列與字串

第 2 種思路可以歸納為「左右邊界向中間走,兩邊夾」,這種思路在解決複雜問題的時候,可以使得思考的過程變得簡單。

思路分析:

首先,插入位置有可能在陣列的末尾(題目中的示例 3),需要單獨判斷。如果在陣列的末尾,插入位置的下標就是陣列的長度;
否則,根據示例和暴力解法的分析,插入位置的下標是 大於等於 target 的第 11 個元素的位置。
因此,嚴格小於 target 的元素一定不是解,在迴圈體中將左右邊界 leftright 逐漸向中間靠攏,最後 leftright 相遇,則找到了插入元素的位置。根據這個思路,可以寫出如下程式碼。

class Solution {
public:
    int searchInsert(vector<int> &nums, int target) {
        int size = nums.size();
        if (size == 0) {
            return 0;
        }

        // 特判
        if (nums[size - 1] < target) {
            return size;
        }
        int left = 0;
        int right = size - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            // 嚴格小於 target 的元素一定不是解
            if (nums[mid] < target) {
                // 下一輪搜尋區間是 [mid + 1, right]
                left = mid + 1;
            } else {
                // 下一輪搜尋區間是 [left, mid] 
                right = mid;
            }
        }
        return left;
    }
};

我自己的思考:
當最後剩兩個元素時,mid就是left。若target<=nums[mid],那麼縮小區間為[left,legt],若left是要找的,則返回left就是所需的,若不是要找的,因為left所在的元素是大於等於target的,所以要插入的位置就是left。若target>nums[mid],則縮小區間為[right,right],若right是要找的,則返回right就是所需的,若不是要找的,怎麼能保證right所在元素一定大於等於target呢?因為一開始對nums陣列的末端元素進行了判斷,所以target一定是小於等於末端元素的。當這最後兩個元素區間是末端兩個自然滿足,而當是中間的元素時,再縮排到兩個元素前,有滿足target小於nums[mid],而縮排區間後mid變為right,也就是target一定小於縮排區間後的right,所以返回的right就是要插入的位置。

複雜度分析:

  • 時間複雜度:O(\log N)O(logN),這裡 NN 是陣列的長度,每一次都將問題的規模縮減為原來的一半,因此時間複雜度是對數級別的;
  • 空間複雜度:O(1)O(1),使用到常數個臨時變數。

給出一個區間的集合,請合併所有重疊的區間。

示例 1:

輸入: intervals = [[1,3],[2,6],[8,10],[15,18]]
輸出: [[1,6],[8,10],[15,18]]
解釋: 區間 [1,3] 和 [2,6] 重疊, 將它們合併為 [1,6].

示例 2:

輸入: intervals = [[1,4],[4,5]]
輸出: [[1,5]]
解釋: 區間 [1,4] 和 [4,5] 可被視為重疊區間。

注意:輸入型別已於2019年4月15日更改。 請重置預設程式碼定義以獲取新方法簽名。

提示:

  • intervals[i][0] <= intervals[i][1]

方法一:排序

思路

如果我們按照區間的左端點排序,那麼在排完序的列表中,可以合併的區間一定是連續的。如下圖所示,標記為藍色、黃色和綠色的區間分別可以合併成一個大區間,它們在排完序的列表中是連續的:

演算法

我們用陣列 merged 儲存最終的答案。

首先,我們將列表中的區間按照左端點升序排序。然後我們將第一個區間加入 merged 陣列中,並按順序依次考慮之後的每個區間:

如果當前區間的左端點在陣列 merged 中最後一個區間的右端點之後,那麼它們不會重合,我們可以直接將這個區間加入陣列 merged 的末尾;

否則,它們重合,我們需要用當前區間的右端點更新陣列 merged 中最後一個區間的右端點,將其置為二者的較大值。

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        if (intervals.size() == 0) {
            return {};
        }
        sort(intervals.begin(), intervals.end());
        vector<vector<int>> merged;
        for (int i = 0; i < intervals.size(); ++i) {
            int L = intervals[i][0], R = intervals[i][1];
            if (!merged.size() || merged.back()[1] < L) {
                merged.push_back({L, R});
            }
            else {
                merged.back()[1] = max(merged.back()[1], R);
            }
        }
        return merged;
    }
};

總結:

  • 沒有對空的vector進行判斷而溢位錯誤,對每道題應該都進行空判,因為返回型別是vector,所以return {};
  • 排序vector只是保證了每個元素左端是從小到大的,而右端可能不是,比如[1,3]和[2,4],合併後應該是[1,4],所以右端應該是取max(merged.back()[1], R)
  • 題解裡直接在if里加入或條件!merged.size(),相當於在merged為空時對他第一次填充,免去了在外面進行第一步的操作,更簡潔。
  • sort(intervals.begin(), intervals.end());,sort函式對於二維陣列,預設是按第一列進行行排列的。`
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章