技術交流QQ群:1027579432,歡迎你的加入!
歡迎關注我的微信公眾號:CurryCoder的程序人生
- 遞歸方法和循環方法的對比
- 遞歸方法代碼實現比較簡潔,但是性能不如循環方法,還有可能出現棧溢出的問題。一般情況下優先考慮遞歸方法來實現!
- 搜索路徑的題目:一般使用回溯法,回溯法很適合使用遞歸方法的代碼來實現!當要求不能使用遞歸實現的時候,考慮使用棧模擬遞歸的過程
-
求某個問題的最優解時,并且該問題可以拆分為多個子問題時:可以嘗試使用動態規劃的方法!在使用自上而下的遞歸思路去分析動態規劃問題時,會發現子問題之間存在重疊
的更小的子問題。為了避免不必要的重復計算,使用自下而上的循環代碼來實現,即把子問題的最優解先計算出來并用數組保存下來,然后基于子問題的解計算大問題的解。 - 特殊情況:在分解子問題的時候存在某個特殊的選擇,采用這個特殊的選擇將一定那個得到最優解,則此題目可能適用于貪心算法!
- 典型題目的解題思路:在一個已經排好序的數組中查找一個數字或者統計某個數字出現的次數,可以嘗試使用二分查找算法!
- Q1:給定一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?找出所有滿足條件且不重復的三元組。注意:答案中不可以包含重復的三元組。
- 自己寫的:暴力解決,時間復雜度太大
class Solution(object):
def threeSum(self, nums):
nums.sort()
result = []
temp = []
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
for k in range(j + 1, len(nums)):
if nums[i] + nums[j] + nums[k] == 0:
result.append([nums[i], nums[j], nums[k]])
for i in range(len(result)):
if result[i] not in temp:
temp.append(result[i])
else:
continue
return temp
if __name__ == "__main__":
s = Solution()
result = s.threeSum([-1, 0, 1, 2, -1, -4, 3, -5, -2, -3])
print(result)
- 網上大神的解法:
class Solution:
def threeSum(self, nums):
# 存儲結果列表
result = []
# 對nums列表進行排序,無返回值,排序直接改變nums順序
nums.sort()
for i in range(len(nums)):
# 因為是升序排列,如果排序后第一個數都大于0,則跳出循環,不可能有為0的三數之和
if nums[i] > 0:
break
# 排序后相鄰兩數如果相等,則跳出當前循環繼續下一次循環,相同的數只需要計算一次
if i > 0 and nums[i] == nums[i-1]:
continue
# 記錄i的下一個位置
j = i + 1
# 最后一個元素的位置
k = len(nums) - 1
while j < k:
# 判斷三數之和是否為0
if nums[j] + nums[k] == -nums[i]:
# 把結果加入數組中
result.append([nums[i], nums[j], nums[k]])
# 判斷j相鄰元素是否相等,有的話跳過這個
while j < k and nums[j] == nums[j+1]: j += 1
# 判斷后面k的相鄰元素是否相等,是的話跳過
while j < k and nums[k] == nums[k-1]: k -= 1
# 沒有相等則j+1,k-1,縮小范圍
j += 1
k -= 1
# 小于-nums[i]的話還能往后取
elif nums[j] + nums[k] < -nums[i]:
j += 1
else:
k -= 1
return result
-
Q2:見下圖
羅馬數字轉整數.jpg - A2:
class Solution: def romanToInt(self, s: str) -> int: d = {'M': 1000,'D': 500 ,'C': 100,'L': 50,'X': 10,'V': 5,'I': 1} result = 0 s_len = len(s) for i in range(s_len-1): if d[s[i]] < d[s[i+1]]: result -= d[s[i]] else: result += d[s[i]] result += d[s[-1]] return result
- Q3:編寫一個函數來查找字符串數組中的最長公共前綴,如果不存在公共前綴,返回空字符串 ""。
- A3:僅僅比較最長與最短的字符串,如果存在相同的前綴就返回;不存在就返回一個空字符串。重要的是如何從兩個字符串中取相同位置的字符進行比較。
def longest_str(strs): s1 = min(strs) # 最短字符串 s2 = max(strs) # 最長字符串 for i, v in enumerate(s1): if v != s2[i]: return s2[:i] # 當第一個字符就不相等時,返回s2[:0]=[],執行下面的if語句 if not strs: return "" if __name__ == "__main__": strs = ["dog", "racecar", "car"] strs1 = ["flower", "flow", "flight"] result = longest_str(strs) result1 = longest_str(strs1) print(result) print(result1)
- Q4:給定一個只包括 '(',')','{','}','[',']' 的字符串,判斷字符串是否有效。有效字符串需滿足:左括號必須用相同類型的右括號閉合;左括號必須以正確的順序閉合;注意空字符串可被認為是有效字符串
- A4:只有完整出現[],{},()的情況才會返回true,同時空字符串也被任何是有效字符串,所以,用空格進行替換[],{},(),然后比較替換后的結果是否是空字符串,不是的話說明不是有效字符串。
def is_Valid(s): while("{}" in s or "()" in s or "[]" in s): s = s.replace("{}", "") s = s.replace("()", "") s = s.replace("[]", "") return s == ""
- Q5:將兩個有序鏈表合并為一個新的有序鏈表并返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。 例如,輸入:1->2->4, 1->3->4;輸出:1->1->2->3->4->4
- A5:
struct ListNode{ int val; struct ListNode *next; }; // 借助歸并排序的思路,遞歸方法實現 struct ListNode* mergeTwoLists(struct ListNode *l1, struct ListNode *l2){ struct ListNode *p; if (!l1) retutn l2; if (!l2) return l1; if(l1->val < l2->val){ // 將兩個鏈表中小的元素放在新的鏈表中,用指針p指向它 p = l1; p->next = mergeTwoLists(l1->next, l2); }else{ p = l2; p->next = mergeTwoLists(l2->next,l1); } return p; }
- Q6:給定一個排序數組,你需要在原地刪除重復出現的元素,使得每個元素只出現一次,返回移除后數組的新長度。不要使用額外的數組空間,你必須在原地修改輸入數組并在使用 O(1) 額外空間的條件下完成。
- A6:
int removeDuplicates(int *nums, int numsSize){ if (numsSize < 2) return numsSize; int i, j=0; for (i=1;i<numsSize;i++){ if(nums[j] != nums[i]) nums[++j]=nums[i]; } return j+1; }
- Q7:給定一個數組 nums 和一個值 val,你需要原地移除所有數值等于 val的元素,返回移除后數組的新長度。不要使用額外的數組空間,你必須在原地修改輸入數組并在使用 O(1) 額外空間的條件下完成。元素的順序可以改變。你不需要考慮數組中超出新長度后面的元素。
- A7:
int removeElement(int* nums, int numsSize, int val){ int i,j=0; for (i=0;i < numsSize;i++){ if(nums[i] != val) nums[j++] = nums[i]; } return j; }
- Q8:統計小于非負整數n的質數的個數。例如。n=10,則輸出小于10的質數個數是4個,具體是2, 3, 5, 7。
- A8:使用厄拉多塞篩法:首先從數字2開始,依次刪除2的倍數;接著從3開始,依次刪除3的倍數,然后從5開始(因為4是2的倍數,已經被刪除了),依次刪除5的倍數。一直循環上面的步驟的n-1即可,然后統計最后剩余的數的個數,即質數的個數。
class Solution:
def countPrimes(self, n: int) -> int:
if n<=2:
return 0
isPrime = [1] * n # 生成一個全為1的列表
isPrime[0], isPrime[1] = 0, 0
for i in range(2, int(n**0.5)+1): # 質數:除1和本身外,沒有其他的因數。如果有其他因數p,則p*p = n,即p = n**0.5
if isPrime[i] == 1: # 如果i是質數
isPrime[2*i:n:i] = [0] * len(isPrime[i*2:n:i]) # 將i的倍數置為0
# print(i, isPrime)
return sum(isPrime)
- Q9:給定一個由整數組成的非空數組所表示的非負整數,在該數的基礎上加一。最高位數字存放在數組的首位, 數組中每個元素只存儲一個數字。你可以假設除了整數 0 之外,這個整數不會以零開頭。
- A9:思路是如果最后一位不是9,而是0到8,就執行普通的最后一位的加1操作;如果最后一位是9,就要考慮向前面一位產生進位標志1,這是此題的關鍵!
class Solution:
def plusOne(self, digits: List[int]) -> List[int]:
flag = False
for i in range(len(digits)-1, -1, -1): # 反向遍歷list(起點,終點,步長)
if digits[i] is 9:
flag = True
digits[i] = 0
else:
digits[i] += 1
return digits
if flag: # 防止出現list=[9]的情況
digits.insert(0, 1)
return digits
- Q10:刪除鏈表中等于給定值 val 的所有節點。示例:輸入: 1->2->6->3->4->5->6, val = 6 輸出: 1->2->3->4->5
- A10:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 空鏈表的情況
if(!head){
return nullptr;
}
// 刪除的節點是頭節點
while(head->val == val){
head = head->next;
if(!head){
return nullptr;
}
}
ListNode* pNode = head;
ListNode* pCur = head->next;
// 刪除的是中間的某個節點
while(pCur){
if(pCur->val == val){
pNode->next = pCur->next;
pCur = pCur->next;
}else{
pNode = pCur;
pCur = pCur->next;
}
}
return head;
}
};
-
Q11:編寫一個算法來判斷一個數是不是“快樂數”。一個“快樂數”定義為:對于一個正整數,每一次將該數替換為它每個位置上的數字的平方和,然后重復這個過程直到這個數變為 1,也可能是無限循環但始終變不到 1。如果可以變為 1,那么這個數就是快樂數。
示例 - A11:
class Solution{
public:
bool isHappy(int n){
int sum = 0;
// 1到9中只有1和7符合快樂數的定義!
if(n == 1 || n==7){
return true;
}
// 其余不符合的情況,都不是快樂數!
if(n<10){
return false;
}
sum = isHappyCore(n);
return isHappy(sum); // 遞歸判斷
}
private:
int isHappyCore(int n){
// 下面的代碼是取一個整數的各個位置上的數,具有一般性,記憶!
int sum = 0
while(n > 0){
int mod = n % 10;
sum += mod * nod;
n /= 10;
}
return sum;
}
}
-
Q12:給定一個排序鏈表,刪除所有重復的元素,使得每個元素只出現一次。
刪除鏈表中的重復元素 - A12:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(!head || head->next == nullptr){
return head;
}
ListNode* pNode = head; // 慢指針
ListNode* pCur = head->next; // 快指針
while(pNode->next != nullptr){
if(pNode->val == pCur->val){ // 找到重復元素
if(pCur->next == nullptr){ // 快指針后面若沒有元素直接剔除
pNode->next = nullptr;
}else{ // 快指針后有元素
pNode->next = pCur->next;
pCur = pCur->next;
}
}else{ //元素不相等
pNode = pNode->next;
pCur = pCur->next;
}
}
return head;
}
};
-
Q13:給定兩個二叉樹,編寫一個函數來檢驗它們是否相同。如果兩個樹在結構上相同,并且節點具有相同的值,則認為它們是相同的。
實例 - A13:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if(p==nullptr && q==nullptr){
return true;
}
if(p != nullptr && q != nullptr && p->val == q->val){
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right); // 在左右子樹上遞歸實現!
}else{
return false;
}
}
};
-
Q14:給定一個二叉樹,檢查它是否是鏡像對稱的。例如,二叉樹 [1,2,2,3,4,4,3] 是對稱的。
實例 - A14:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
// 如果是對稱二叉樹,則從左子樹開始遍歷與從右子樹開始遍歷時,遍歷的結果都相同!
class Solution {
public:
bool isSymmetric(TreeNode* root) {
return isMirror(root,root); // 遞歸實現
}
bool isMirror(TreeNode* root1, TreeNode* root2){
if(root1 == nullptr && root2 == nullptr){
return true;
}
if(root1 == nullptr || root2 == nullptr){
return false;
}
return (root1->val == root2->val) && isMirror(root1->left, root2->right) && isMirror(root1->right, root2->left);
}
};
-
A15:給定一個大小為 n 的數組,找到其中的眾數。眾數是指在數組中出現次數大于 ? n/2 ? 的元素。
求眾數 - Q15:
class Solution:
def majorityElement(self, nums: List[int]) -> int:
# nums.sort()
# nums_len = len(nums)
# return nums[nums_len // 2] # 返回中間的數
candidate = None # 摩爾投票法
count = 0
for num in nums:
if num == candidate: # 如果數組中的下一個元素num與candidate相同,就不會碰撞,此時count加1
count += 1
elif count > 0: # 如果數組中的下一個元素num與candidate不同,就會發生碰撞,此時count減1,candidate維持上一次的數據
count -= 1
else:
candidate, count = num, 1 # 第一次進入循環,candidate是第一個元素,count加1
return candidate
- A16:實現一個函數,將字符串中的每個空格替換成%20。例如,輸入“hello world.”,則輸出"hello%20world."
- Q16:解題思路:觀察出空格替換后原始字符串變長的關系。在原始字符串的基礎上進行修改,利用觀察出的關系,使用兩個指針從后向前移動將字符串從原始字符串復制到新的字符串中。
#include <iostream>
#include <string>
using namespace std;
// 解題思路:在原始字符串的基礎上進行修改,注意原始字符串有足夠的空間。使用兩個指針,發現空格數量與原始字符串增加的長度關系!
class Solution{
public:
void ReplaceSPace(char* str, int len){
if(str == nullptr || len <= 0){
return;
}
int original_len = 0;
int number_blank = 0;
int i=0;
// 遍歷原始字符串,統計空格的數目
while(str[i] != '\0'){
++original_len;
if(str[i] == ' '){
++number_blank;
}
++i;
}
int new_len = original_len + 2 * number_blank;
if(new_len > len){
return;
}
int original_index = original_len;
int new_index = new_len;
while(original_index >= 0 && new_index > original_index){
if(str[original_index] == ' '){
str[new_index--] = '0';
str[new_index--] = '2';
str[new_index--] = '%';
}else{
str[new_index--] = str[original_index];
}
original_index--;
}
}
};
- Q17:單向鏈表的基礎操作:在單向鏈表的末尾插入一個節點和找到第一個值為value的節點并將其刪除
- A17:注意不要忘記釋放在堆空間上申請的動態內存
#include <iostream>
using namespace std;
struct ListNode{
ListNode* m_pNext;
int m_pVal;
};
// 在鏈表的末尾插入一個節點
void AddNodeToTail(ListNode** pHead, int value){
// 為新插入的節點分配空間
ListNode* pNew = new ListNode();
pNew->m_pNext = nullptr;
pNew->m_pVal = value;
if(pHead == nullptr){ // 空鏈表
*pHead = pNew;
}
else{
ListNode* pNode = *pHead;
while(pNode->m_pNext != nullptr){
pNode = pNode->m_pNext;
}
pNode->m_pNext = pNew;
}
}
// 找到第一個含某值value的節點并刪除此節點
void RemoveNode(ListNode** pHead, int value){
if(pHead == nullptr || *pHead == nullptr){
return;
}
ListNode* pToDeleted = nullptr;
if((*pHead)->m_pVal == value){ // 頭節點就是要刪除的那個節點
pToDeleted = *pHead;
*pHead = (*pHead)->m_pNext;
}else{ // 頭節點不是要刪除的那個節點
ListNode* pNode = *pHead;
while(pNode->m_pNext != nullptr && pNode->m_pNext->m_pVal != value){ // 頭節點不是要刪除的那個節點,后面的節點也沒有出現value,則一直向后查找
pNode = pNode->m_pNext;
}
if(pNode->m_pNext != nullptr && pNode->m_pNext->m_pVal == value){ // 頭節點不是要刪除的那個節點,后面的節點找到了value,則執行刪除操作
pToDeleted = pNode->m_pNext;
pNode->m_pNext = pNode->m_pNext->m_pNext;
}
}
if(pToDeleted != nullptr){
delete pToDeleted;
pToDeleted = nullptr;
}
}
- Q18:從尾到頭反向打印出單向鏈表
- A18:因為單向鏈表方向不能反過來,如果將指針反過來來實現改變單向鏈表的方向。但是,這會改變單向鏈表的數據結構,故在不改變數據結構的基礎上,使用棧來實現!
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
struct ListNode{
ListNode* m_pNext;
int m_pVal;
};
// 利用棧這個數據結構,后進先出!因為單向鏈表方向不能反過來,如果將指針反過來,從而實現改變鏈表的方向,但是這會改變鏈表的數據結構,故在不改變數據結構的基礎上,使用棧來實現
class Solution{
public:
vector<int> printListFromTailToHead(ListNode* pHead){
stack<int> nodes;
vector<int> result;
ListNode* pNode = pHead;
while(pNode != nullptr){
nodes.push(pNode->m_pVal);
pNode = pNode->m_pNext;
}
while(!nodes.empty()){
result.push_back(nodes.top());
nodes.pop();
}
return result;
}
};
- Q19: 網易2020秋招筆試題:小易有一個長度為n的數字數組,a1, a2,..., an,問你能否用這n個數字構成一個環(首尾相連),使得環中的每一個數字都小于它相鄰的兩個數字的和(每個數字都必須使用并且每個數字只能使用一次)。
輸入描述:第一行包含一個整數t(1<=t<=10),表示測試樣例的組數,每個測試樣例輸入如下:第一行一個整數n表示數字的個數,第二行輸入數組中的n個元素,每兩個整數之間用一個空格隔開。
輸入樣例:
1 1
5 3
17 6 17 11 17 1 2 3
輸出樣例:
YES NO
輸出描述:輸出應該包含t行,對每組測試樣例,如果能輸出“YES”,否則輸出“NO”
- A19:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int main(){
int t; // 總共t個數組
scanf("%d", &t);
while(t--)
{
int n; // 每個數組含有的元素個數
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
sort(a, a+n); // 先對數組進行排序,除了最后一個數字,都滿足相鄰兩個數字大于自己
swap(a[n-2], a[n-1]); // 對于最后一個數字,交換最后兩個數字,判斷是否滿足條件即可。
bool f = 0;
for(int i = 0; i < n; i++)
{
int pre = (i-1+n) % n;
int sub = (i+1) % n;
if(a[pre] + a[sub] <= a[i]) f = 1;
}
if(f) cout << "NOT" << endl;
else cout << "YES" << endl;
}
return 0;
}