leetcode前50題easy難度題解

1:兩數和

題目:
給定一個數判斷是否爲迴文數。
求解方法:

  1. hash表查詢target-cur是否已經出現。時間複雜度爲o(n),空間複雜度爲o(n)
  2. 穩定排序+兩指針。時間複雜度爲o(nlogn),空間複雜度爲o(n);【該方法leetcode無法AC】

注意事項:

  1. unordered_map的find是在hash表中查找key,返回iterator

    m.find(key)!=m.end()
    
  2. vector res不能先申請兩個int大小的空間,後面再push_back之後會返回大小爲4的數組!

代碼:

class Solution {
public:
	/**
	 * @param numbers: An array of Integer
	 * @param target: target = numbers[index1] + numbers[index2]
	 * @return: [index1, index2] (index1 < index2)
	 */
	vector<int> twoSum(vector<int>& nums, int target) {
		return twoSumWayI(nums, target);
	}
	// way1:hash表;o(n) time,o(n) space
	vector<int> twoSumWayI(vector<int> nums, int target) {
		unordered_map<int, int> m;//hash表記錄元素出現的下標
		vector<int> result; //大小爲2的結果存儲空間,初始化爲-1
		// 邊界
		int n = nums.size();
		/*if (n == 0 || n == 1) {
			return result;
		}*/
		// 遍歷每個元素,同時檢查target-當前val是否已經出現過(hash)
		for (int i = 0; i < n; i++) {
			int checkNum = target - nums[i];
			if (m.find(checkNum) != m.end()) {// target-nums[i]出現次數不爲0即出現過,已經找到答案
				result.push_back(m[checkNum]);
				result.push_back(i);
				return result;
			}
			else {
				m[nums[i]] = i;//否則記錄當前
			}

		}
		return result;
	}
	// way2:排序+兩指針;o(nlogn) time,o(1) space;此題不可取,因爲需要返回下標,而排序之後下標已經改變(除非用穩定的歸併排序o(nlogn)time,o(1)sapce)
	vector<int> twoSumWayII(vector<int> &nums, int target) {
		vector<int> result;
		// merge sort in asc
		mergeSort(nums, 0, nums.size());
		// 兩相向指針
		for (int i = 0, j = nums.size() - 1; i < j; i++) {
			if (nums[i] + nums[j] == target) {
				result.push_back(i);
				result.push_back(j);
				return result;
			}
			else if (nums[i] + nums[j] < target) {
				i++;
			}
			else {
				j--;
			}
		}
		return result;
	}
	// merge排序-devide and conqure;o(nlogn) time,o(1) space
	void mergeSort(vector<int> &nums, int left, int right) {
		// devide:devide n-problem to n/2 probelm;o(1) time
		int mid = left + (right - left) / 2;
		mergeSort(nums, left, mid);
		mergeSort(nums, mid + 1, right);
		// conqure:merge two sorted array;o(n) time
		merge(nums, left, mid, right);

	}
	// merge two sorted array in asc;o(n) time,o(1) space
	void merge(vector<int> &nums, int left, int mid, int right) {
		int n = nums.size();
		if (n == 0 || n == 1) {
			return;
		}
		int lens1 = mid - left + 1, lens2 = right - mid;//size of two subarray
		int i = 0, j = 0,k = 0;
		while (i < lens1 && j < lens2) {
			if (nums[i] <= nums[j]) {
				nums[k++] = nums[i++];
			}
			else {
				nums[k++] = nums[j++];
			}
		}
		while (i < lens1) {
			nums[k++] = nums[i++];
		}
		while (j < lens2) {
			nums[k++] = nums[j++];
		}
	}
};


7:整數反轉

題目:
給定一個數判斷是否爲迴文數。
求解方法:

  1. 數字彈出推入 & 溢出檢查。判斷字符串是否爲迴文串。時間複雜度o(n)o(n),空間複雜度爲o(n)o(n)。時間複雜度爲o(log(x))o(log(x)),空間複雜度爲o(1)o(1)。因爲x每次變爲原來的10倍,類似於二分o(log2n)o(log_2^n)

注意事項:

  1. x將最右邊以爲pop出來:

    int pop = x %10;
    x = x/10;
    
  2. 將pop加到rev後面之前先判斷溢出,因爲rev=rev10+poprev= rev*10+pop會導致溢出。對溢出情況進行分析:

    • 正數越界

      rev10+pop&gt;INT32_MAXrev&gt;(INT32_MAXpop)10rev*10+pop &gt; INT32\_MAX \\ rev &gt; \frac{(INT32\_MAX-pop)}{10}
      正數越界溢出情況細分爲兩種:
      rev{INT32_MAX10==INT32_MAX10pop&gt;7pop(INT32MAX)10 rev \begin{cases} 大於\frac{INT32\_MAX}{10} \\ ==\frac{INT32\_MAX}{10} &amp; \text{pop&gt;7}即pop \frac{(INT32_MAX)}{10} \end{cases}

    • 負數越界
      rev10+pop&lt;INT32_MINrev&lt;(INT32_MINpop)10rev*10+pop &lt; INT32\_MIN \\ rev &lt; \frac{(INT32\_MIN-pop)}{10}
      負數越界溢出情況細分爲兩種:
      rev{INT32_MIN10==INT32_MIN10pop&lt;-8pop&lt;(INT32MIN)10 rev \begin{cases} 小於\frac{INT32\_MIN}{10} \\ ==\frac{INT32\_MIN}{10} &amp; \text{pop&lt;-8}即pop&lt;\frac{(INT32_MIN)}{10} \end{cases}

    • 越界判斷代碼

      if (rev > INT32_MAX / 10 || (rev == INT32_MAX / 10 && pop > INT32_MAX % 10)) {//正數溢出
      		return 0;
      }
      if (rev < INT32_MIN / 10 || (rev == INT32_MIN / 10 && pop < INT32_MIN % 10)) {//負數溢出
      	return 0;
      }
      

代碼:

class Solution {
public:
    int reverse(int x) {
		return reverseWayIPopPush(x);
	}
	// 數字彈出與推入 & 溢出判斷;o(log(x)) time,o(1) space
	int reverseWayIPopPush(int x) {
		if (x<INT32_MIN || x>INT32_MAX) { // 溢出返回0
			return 0;
		}
		int rev = 0;
		while (x != 0) {//當x不等於0時一直循環
			// 將x的最右一位數字pop出來
			int pop = x % 10;
			x = x / 10;

			// 溢出判斷
			if (rev > INT32_MAX / 10 || (rev == INT32_MAX / 10 && pop > INT32_MAX % 10)) {//正數溢出
				return 0;
			}
			if (rev < INT32_MIN / 10 || (rev == INT32_MIN / 10 && pop < INT32_MIN % 10)) {//負數溢出
				return 0;
			}
			// 將pop出來的數push到rev的後面
			rev = rev * 10 + pop;
		}
		// 返回結果
		return rev;
	}

};

344:翻轉字符串

題目:
給定一個數判斷是否爲迴文數。
求解方法:

  1. 兩相向指針。時間複雜度o(n)o(n),空間複雜度爲o(1)o(1)
  2. 庫函數reverse

代碼:

class Solution{
public:
	// 要求原地反轉字符串
	void reverseString(vector<char>& s) {
		reverseStringWayII(s);
	}
	// 方法1:兩相向指針;o(n) time,o(1) space
	void reverseStringWayI(vector<char>& s) {
		int n = s.size();//字符串長度
		for (int i = 0, j = n - 1; i <= j;) {
			swap(s[i++], s[j--]);
		}
	}
	//方法2:利用庫函數;比方法1快
	void reverseStringWayII(vector<char>& s) {
		reverse(s.begin(), s.end());
	}
};

9:迴文數

題目:
給定一個數判斷是否爲迴文數。
求解方法:

  1. 數字轉字符串。判斷字符串是否爲迴文串。時間複雜度o(n)o(n),空間複雜度爲o(n)o(n)
  2. 反轉一半數字。反轉數字後半部分,判斷是否和前半部分相等或者是否爲前半部分的10倍。時間複雜度爲o(log(x))o(log(x)),空間複雜度爲o(1)o(1)

注意事項:

  1. 越界情況:負數、非0且爲10的倍數
  2. 判斷已經翻轉了一半:reverseNum>=x時候(長度大於x時肯定大於x)
  3. 最後判斷x和reverseNum是否相等(偶數位)或者reverseNum是x的10倍(奇數位)

注:reverseNum是數字後半部分reverse之後的結果

代碼:

class Solution {
public:
	bool isPalindrome(int x) {
		return isPalindromeWayII(x);
	}
	// way1:將數字轉化爲字符串;o(n) time,o(n) space
	bool isPalindromeWayI(int x) {
		string str = to_string(x);
		// 判斷字符串是否爲迴文
		for (int i = 0, j = str.size() - 1; i <= j;) {
			if (str[i] == str[j]) {
				i++;
				j--;
			}
			else {
				return false;
			}
		}
		return true;
	}
	// way2:不將數字轉化爲字符串,而是反轉字符串本身,若反轉前後相同則爲palidrom number;
	// o(log(x)) time,o(1) space
	// 但數字反轉容易導致溢出,爲了避免數字反轉可能導致的溢出問題。考慮只反轉數字的一半,如果該數字是迴文,其後半部分反轉後應該與原始數字的前半部分相同。
	//例如,輸入 1221,我們可以將數字 “1221” 的後半部分從 “21” 反轉爲 “12”,並將其與前半部分 “12” 進行比較,因爲二者相同,我們得知數字 1221 是迴文

	/*算法

		(1)首先,我們應該處理一些臨界情況。
			所有負數都不可能是迴文,例如: - 123 不是迴文,因爲 - 不等於 3。所以我們可以對所有負數返回 false。
			當數字末尾爲0時,要求第一位爲0。所以當數字是10的倍數但不是0的時候就返回false。

		(2)現在,讓我們來考慮如何反轉後半部分的數字。
		對於數字 1221,如果執行 1221 % 10,我們將得到最後一位數字 1,要得到倒數第二位數字,我們可以先通過除以 10 把最後一位數字從 1221 中移除,1221 / 10 = 122,再求出上一步結果除以 10 的餘數,122 % 10 = 2,就可以得到倒數第二位數字。如果我們把最後一位數字乘以 10,再加上倒數第二位數字,1 * 10 + 2 = 12,就得到了我們想要的反轉後的數字。如果繼續這個過程,我們將得到更多位數的反轉數字。

		(3)現在的問題是,我們如何知道反轉數字的位數已經達到原始數字位數的一半?

		我們將原始數字除以 10,然後給反轉後的數字乘上 10,所以,當原始數字小於反轉後的數字時,就意味着我們已經處理了一半位數的數字。
	*/
	bool isPalindromeWayII(int x) {
		if (x < 0 || (x % 10 == 0 && x != 0)) {
			return false;
		}
		int reverseNumber = 0;
		while (x > reverseNumber) { // 當彈出最右側數字push到reverseNumber後,如果reverseNumber>x則表示已經處理了一半了
			int pop = x % 10;
			x = x / 10;
			reverseNumber = reverseNumber * 10 + pop;
		}

		// 判斷前半和後半翻轉後的結果是否相同
		// 分奇數和偶數位,偶數位要求x=reverseNumber,奇數位要求x = reverseNum /10(reverse/10相當於去除中位數)
		return x == reverseNumber || x == reverseNumber / 10;
	}
};

羅馬數字轉整數

題目:
羅馬字符共七個,每個均代表一定的數字,將羅馬字符串轉化爲數字。
思路:

  1. hash表存儲羅馬字符和數字的映射關係。比較當前字符和下一個字符所對應的數字大小決定加還是減當前字符。

代碼:

class Solution {
public:
	// 首先建立一個HashMap來映射符號和值,然後對字符串從左到右來,如果當前字符代表的值不小於其右邊,就加上該值;否則就減去該值。以此類推到最左邊的數,最終得到的結果即是答案
	int romanToInt(string s) {
		int n = s.size();
		int sum = 0;
		// 構建Hashmap
		unordered_map<char, int> m;
		m['I'] = 1;
		m['V'] = 5;
		m['X'] = 10;
		m['L'] = 50;
		m['C'] = 100;
		m['D'] = 500;
		m['M'] = 1000;

		for (int i = 0; i < n; i++) {
			if (i == n - 1) {//最後一位
				sum += m[s[i]];
			}
			else {
				int firstValue = m[s[i]];
				int nextValue = m[s[i + 1]];
				if (firstValue >= nextValue) {
					sum += firstValue;
				}
				else {
					sum -= firstValue;
				}

			}
		}
		return sum;
	}
};

14:最長公共前綴

題目:
給定字符串數組,找到數組中所有字符串的公共前綴的長度。
思路:

  1. 找到數組中ASCII碼最大s1和最小的字符串s2;只需要得到s1和s2的最大公前綴即可。

注意事項:

  1. 利用max_elementmin_element函數找到容器中最大和最小元素所對應的迭代器
  2. 利用mismatch找到兩個字符串第一個不匹配的位置的(2個)迭代器。
  3. auto類型的s1,要使用s1->begin()而不是s1.begin()

代碼:

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
		if (strs.size() == 0) {
			return "";
		}
		// 利用max_element和min_element得到容器的最大值和最小值
		auto s1 = max_element(strs.begin(), strs.end());
		auto s2 = min_element(strs.begin(), strs.end());
		// equal和mismatch算法的功能是比較容器中的兩個區間內的元素。
        //這兩個算法各有3個參數first1,last1和first2.如果對於區間[first1,last1)內所有的first1+i,first1+i和first2所在位置處的元素都相等,則equal算法返回真,否則返回假。
        //mismatch算法的返回值是由兩個迭代器first1+i和first2+i組成的一個pair,表示第1對不相等的元素的位置。如果沒有找到不相等的元素,則返回last1和first2+(last1-first1)。因此,語句
		auto pair = mismatch(s1->begin(), s1->end(), s2->begin());
		string result = string(s1->begin(), pair.first);
		return result;
	}
};

20:有效的括號

題目:
檢查字符串中的括號是否配對。
思路:

  1. 。當爲左括號時入展,右括號時檢查,若棧空或者棧頂元素與當前元素不配對則返回false,否則將棧頂元素彈出。
  2. 時間複雜度爲o(n)o(n),空間複雜度爲o(n)o(n)

代碼

// 有效的括號
class IsValidA {
public:
	bool isValid(string s) {
		int n = s.size();
		if (n == 0) {//空字符是有效的括號
			return true;
		}
		stack<int> stacks;
		for (int i = 0; i < n; i++) {
			if (s[i] == '[' || s[i] == '{' || s[i] == '(') {
				stacks.push(s[i]);
			}
			else {
				if (stacks.empty()) return false;
				if (s[i] == '}' && stacks.top() != '{') return false;
				if (s[i] == ')' && stacks.top() != '(') return false;
				if (s[i] == ']' && stacks.top() != '[') return false;
				stacks.pop();
			}
		}
		return stacks.empty();
	}
};

21:合併兩個有序鏈表

題目:
將兩個有序鏈表合併爲一個有序鏈表。
思路:

  1. 利用dummy node.因爲兩個鏈表不確定哪一個的node作爲頭節點,當頭節點不確定或者改變時候使用dummy node
  2. 時間複雜度o(n+m)o(n+m),空間複雜度o(n+m)o(n+m)

代碼:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    // 因爲不確定哪一個是合併後的頭節點,所以建立dummy Node
	ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
		ListNode* dummy = new ListNode(0);
		ListNode* head = dummy;

		while (l1 != NULL && l2 != NULL) {
			if (l1->val <= l2->val) {
				head->next = l1;
				l1 = l1->next;
			}
			else {
				head->next = l2;
				l2 = l2->next;
			}
			head = head->next;
		}
		if (l1 != NULL) {
			head->next = l1;
		}
		if (l2 != NULL) {
			head->next = l2;
		}
		return dummy->next;
	}
};

26:刪除排序數組中的重複項

題目:
給定一個排序數組,你需要在原地刪除重複出現的元素,使得每個元素只出現一次,返回移除後數組的新長度。

不要使用額外的數組空間,你必須在原地修改輸入數組並在使用 O(1) 額外空間的條件下完成。
思路:

  1. 兩同向指針。最初均指向0,jj指向不重複元素的最後一位,ii一直往前走;當nums[j]nums[j]不等於nums[i]nums[i]時候,把nums[i]nums[i]的值賦給nums[++j]nums[++j],即賦值給jj的下一位,同時jj向右移動;最後返回j+1。【因爲讓返回數組長度且j指向符合條件的最後一位】
  2. 時間複雜度o(n)o(n),空間複雜度o(1)o(1)

代碼:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
		int n = nums.size();
		if (n == 0) {
			return 0;
		}
		int i = 0, j = 0;
		for (; i < n; i++) {
			if (nums[i] != nums[j]) {
				nums[++j] = nums[i];
			}
		}
		return j + 1;
	}
};

27:移除元素

題目:
給定一個數組 nums 和一個值 val,你需要原地移除所有數值等於 val 的元素,返回移除後數組的新長度。

不要使用額外的數組空間,你必須在原地修改輸入數組並在使用 O(1) 額外空間的條件下完成。

元素的順序可以改變。你不需要考慮數組中超出新長度後面的元素。
思路:

  1. 兩同向指針。最初均指向0,jj指向符合條件區域的下一位,ii一直往前走;當nums[i]nums[i]不等於valval時候,把nums[i]nums[i]的值賦給nums[j++]nums[j++],即賦值給jj的同時將jj向右移動;最後返回j。【因爲讓返回數組長度並且j指向符合條件區域的下一位】
  2. 時間複雜度o(n)o(n),空間複雜度o(1)o(1)

代碼:

//27:原地移除數組中值爲val的數,返回數組長度
class Solution {
public:
	int removeElement(vector<int>& nums, int val) {
		int j = 0;
		for (int i = 0; i < nums.size(); i++) {
			if (nums[i] != val) {
				nums[j++] = nums[i];
			}
		}
		return j;
	}
};

28:實現strStr()

題目:
實現 strStr() 函數。

給定一個 haystack 字符串和一個 needle 字符串,在 haystack 字符串中找出 needle 字符串出現的第一個位置 (從0開始)。如果不存在,則返回 -1。
當 needle 是空字符串時,我們應當返回什麼值呢?這是一個在面試中很好的問題。

對於本題而言,當 needle 是空字符串時我們應當返回 0 。這與C語言的 strstr() 以及 Java的 indexOf() 定義相符。

思路:

  1. 暴力求解。時間複雜度爲o(nm)o(n*m),空間複雜度爲o(1)o(1)。利用兩隻指針,初始iijj均指向0,當iijj任意一個沒有到達最後時遍歷。分爲2種情況,(1)當前字符對應相等,則iijj同時向右移動;(2)若不想等,則將子字符串向右移動一位,即ii進行回溯,jj歸0。以代碼形式則爲:
    i = i-(j-1);
    j=0;
    
  2. 利用庫函數find
  3. 利用kmp算法

代碼:

class Solution {
public:
	int strStr(string haystack, string needle) {
		return strStrWayI(haystack, needle);
	}
	// way1:暴力求解o(n*m)--不能是needle.size()==0而是needl.empty()時候返回0
	int strStrWayI(string haystack, string needle) {
		if (needle.empty()) {// 當子串爲空的時候返回0
			return 0;
		}
		int lens1 = haystack.size(), lens2 = needle.size();
		int i = 0, j = 0;
		while (haystack[i] != '\0' && needle[j] != '\0') {
			if (haystack[i] == needle[j]) {
				i++;
				j++;
			}
			else {
				i = i - j + 1;
				j = 0;
			}
		}
		if (needle[j] == '\0') {
			return i - j;
		}
		else {
			return -1;
		}
	}
	//way2:利用庫函數find
	int strStrWayII(string haystack, string needle) {
		if (needle.size() == 0) {
			return 0;
		}
		int pos = haystack.find(needle);
		return pos;
	}
	// way3:KMP
	// way4:BM
};

35:搜索插入位置

題目:
思路:

  1. 暴力掃描
  2. 二分法

代碼:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        return searchInsertWayII(nums,target);
    }
	// way1:掃描;時間複雜度爲o(n),空間複雜度爲o(1)
	int searchInsertWayI(vector<int>& nums, int target) {
		int n = nums.size();
		if (n == 0) {
			return 0;
		}
		// 暴力搜索即可
		for (int i = 0; i < n; i++) {
			if (nums[i] >= target) {
				return i;
			}
		}
		return n;
	}
	// way2:二分;時間複雜度爲o(logn),空間複雜度爲o(1)
	int searchInsertWayII(vector<int>& nums, int target) {
		// 找到第一個大於等於
		int n = nums.size();
		if (n == 0) {
			return 0;
		}
		int left = 0, right = n - 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;
			}
		}
        // 左邊第一個大於等於
		if (nums[left] >= target) {
			return left;
		}else{
            return left+1;
        }
	}
};
  • 38:Count and Say
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章