日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【OfferX】常见题目

發布時間:2023/12/20 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【OfferX】常见题目 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

編程要素

1.代碼要完全對應思考的邏輯
每一個分支都要熟悉它是在做什么,對應的是哪一部分思考
2.考慮special case
常見的special case如下:
i.整數的最大值: 2147483647, -2147483648
ii.構成整數的數據,不能以0開頭:179. Largest Number
iii.

3.sort的坑
當使用sort時,如果按照下面的方式實現排序,可能存在溢出的情況

Arrays.sort(nums, (int a,int b)->{ return a-b;});

因為如果a-b的值超過int的范圍,就會發生反轉,這里應當如下使用:

Arrays.sort(nums, Comparator.comparingInt((a)->a));

while(n-->0) 循環n次

Java常用的數據結構

數據結構類名支持操作內部實現
PriorityQueueremove() 彈出頭部; remove(i) 移除元素–復雜度O(n)數組
二叉樹TreeMap
隊列Queueremove, removeLast()鏈表LinkedList
快速排序

二進制

1.位操作

n-1 將n的最右側的第一個1以及余下的0取反

n&(n-1) 將n的最右側都一個1置0

n&(n-1) == 0 判斷一個數字是否是2的冪(是否只包含一個1)

n & (k-1) 求n%k,即模,k是2的冪。

n >>>k 即 n/(2^k),對2的冪做除法可以使用移位運算

c=0;while( (n>>>=1)!=0)++c; n是2的冪,求n的階

n^0=n 0與任何數異或,結果不變

使用異或交換兩個數字

int a=1,b=2; a=a^b; b=a^b; a=a^b;

位運算細節:
n>>1 在java中,n是有符號數,則右移會導致補1
n>>>1 補0(盡量使用該操作)

2.數組中有一個數字出現了一次,其他數字都出現了兩次,求這個數

使用異或運算,結果就是該數字。

3.數組中有兩個數字出現了一次,其他數字都出現了兩次,求這兩個數

按第0位是0或1對數組中的數字進行分類,得兩個分組,如果兩個分組的數量是奇數,則說明兩個數字被分配在不同的組里面,分別使用異或即可求得;否則,兩個數字的尾號是相同的,可能存在于其中任何一組

然后按第1位是0或者1對數組進行分類,如果仍然是偶數,按第3位…重復下去,直到有一位能夠將兩個數分到不同的組(其實這個過程就是查找兩個數的第一位不同)

最后找到兩組都為奇數,然后使用異或即可求得兩個數。

關鍵點:尋找兩個數的第一個不同的數字出現的位置

加速:由于知道異或結果,所有可以快速找出第一位不同的位置,然后按這個位置進行分組即可。

4.數組中一個數字出現了一次,其他數字出現了三次,求出現一次的數字

仍然按第0位對數字進行分組,則數量mod 3=1的那一組就是該數所在的組,確定第0位

然后在剩下的組里面按第1位進行分組,數量mod 3 = 1的那一組仍然是該數所在的組,確定第1位

以此類推。

關鍵點:按位分組,確定數字所在的組

5.計算[0,n]上各個數的1的個數

n&(n-1) 可以將第一個1置為0,從而循環n次即可求得count(n)

所以存在遞推: count(n) = count(n & (n-1)) + 1
可以使用緩存進行計算,只需要保證計算i時,小于等于i的所有數字都已經計算完成,從而在O(n)時間內完成計算。

6.不用比較,使用位運算返回a,b中的最大值

其實最大值由最高位1所在的位置決定

排序和查找

1.快速排序

以A[i]為基準元素,第1輪排序的結果是什么?
快速排序偽代碼參考https://blog.csdn.net/xhdxhdxhd/article/details/104229669

2.二分查找

二分查找

5.歸并排序

題目:合并K個排序的數組

3.分塊查找

思想:首先

4.二叉查找樹

關鍵點:極限情況下的數高度是n,所有退化為普通的有序表。

鏈表

1.反轉鏈表

2.刪除鏈表倒數第K個節點

關鍵點:刪除單向鏈表節點的關鍵是定位到前一個節點
不考慮遞歸,使用循環,首先需要遍歷鏈表獲得鏈表大小n,重新遍歷鏈表獲得鏈表的倒數第K+1個節點,也就是指針移動 n - K - 1次。
如果使用遞歸,則需要在每個狀態下記錄當前位置i,在最終狀態下記錄n,則倒數第K+1個節點的位置 n - i = K + 1, i = n - K - 1

static n = 0 # 調用 removeLastKthNode(N,0)即可 removeLastKthNode(N,i):if N.next == NULL:n = i+1else:removeLastKthNode(N.next,i+1)if i == n - K - 1 and N.next != NULL:tmp = N.nextN.next = tmp.nexttmp.next = NULL

2.鏈表的環

題目:判斷鏈表是否存在環, 并返回環的入口
第一步,使用兩個指針,前者步長為2,后者步長為1遍歷鏈表,如果有一個指針為空,則沒有環;否則它們必然相遇在環中的某個點。

第二部,首先計算環的長度n,然后重新使用兩個指針,第一個指針先走n步,然后它們以步長1遍歷鏈表,最終相遇的點就是環的入口。

證明:待續
0 1 2 3 4 5 6 7 8 9 5 6 7 8 9

設環的長度是n,初始部分長度是k。

我們設指針在第i輪的位置是f(i)

(i>k)
顯然 f(i,1) = (i - k + 1)%n

其實上面的算法還是復雜了,維護指針時,在快慢指針相遇時,只需要從頭指針開始同步遍歷直到相遇即可。

public class Solution {public ListNode detectCycle(ListNode head) {ListNode slow = head, fast = head, start = head;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) {while (slow != start) {slow = slow.next;start = start.next;}return slow;}}return null;} }

還可以考慮將鏈表反轉

ListNode* reverseList(ListNode* head) {ListNode* prev = NULL;ListNode* follow = NULL;while (head){follow = head->next;head->next = prev;prev = head;head = follow;}return prev; } bool hasCycle(ListNode *head) {ListNode* rev = reverseList(head);if (head && head->next && rev == head){return true;}return false; }

4.兩個鏈表的第一個公共點

需要從尾端對其兩個鏈表,因此需要兩個棧,來存儲兩個鏈表節點。然后從棧頂開始比較,記錄最后相等的那個節點,就是公共點。

5.移除指定元素

題目:203. Remove Linked List Elements

:保持循環不變式:prev.next = head

class Solution {public ListNode removeElements(ListNode head, int val) {while(head!=null && head.val==val)head=head.next;if(head==null)return null;ListNode res = head;ListNode prev = head;head = head.next;// invariant: prev.next = headwhile(head!=null){if(head.val==val){head = prev.next = head.next;}else{prev = prev.next;head = head.next;}}return res;} }

1.設計一個getMin,push, pop為O(1)時間的棧

對正常棧的狀態進行記錄,棧的棧頂元素稱為狀態,每個元素都記錄其作為棧頂元素時的最小值。入棧時,根據加入的值與棧頂最小值比較,然后更新;出棧時,最小棧彈出棧頂元素。
第二個輔助棧稱為最小棧。

2.使用兩個棧實現隊列

一個棧負責壓入,另一個棧負責彈出。當需要彈出元素時,把第一個棧的所有元素都彈入第二個棧中,實現了順序的逆轉。
只要第二個棧有元素,就優先彈出。

1.從數組構建一個堆

堆有兩個關鍵操作:1.冒泡(siftUp) 2.下沉(siftDown)

以最小堆為例,新增元素時,從最底部開始,向上冒泡,如果小于父元素,與父元素交換即可。此操作命名為siftUp

抽象幾個操作: parent(i), left(i), right(i) 分別表示節點i的父節點,左子節點,右子節點
一種實現:數組arr[0]用于存儲數量;
數組中父節點計算: parent(i) = i/2
左子節點:2i, 右子節點:2i + 1

偽代碼:

siftUp(arr,i):while i > 1 and arr[i] < arr[i/2] :exchange arr[i], arr[i/2]i = i/2siftDown(arr,i):while i < n/2:find min of arr[i], arr[2*i], arr[2*i+1] as arr[k]if k != i:exchange arr[i], arr[k]i=kelse:break

二叉樹

1.前序,中序,后序遍歷確定位置

首先通過先序遍歷遍歷,確定根節點

2.構建遍歷鏈表

關鍵點:使用靜態變量記錄遍歷順序

3.紅黑樹

紅黑樹的幾種翻轉操作, LL, LR, RL, RR等幾種類型。

數組

1.最小的k個數

使用一個大小為k的最大堆維護最小的k個數,每當新增元素時,如果該元素小于最大堆的值,說明它更小,則移除頭部,將其加入到堆中。

2.數組中出現次數超過一半的數字

解1:實際上,該數組的中位數一定是這個數字。所以使用一個最大堆和一個最小堆仿照上面的解法可以解決這個問題,只不過還有更優的解法。
解2:因為這個元素排序后一定出現在中間位置,所以實際上可以轉化為尋找數組的第n/2個元素。設f(A,i,j,k)為在數組A[i,j]中尋找第k大的元素,則
如果將A[i,j]分區為[i,r],[r+1,j]兩個部分,
如果[i,r]元素大于等于k,則重復子問題f(A,i,r,k)即可;
如果[i,r]元素小于k,則在右邊查找, f(A,r+1,r,k-i+r-k)

約束條件:1<=k<=r-i+1
終止條件:i==j

使用Lomuto分區,可以保證每次確定第r個元素;使用Hoare分區則只能將問題規模降為1。
解3:(暫時沒有理解為什么這樣能夠選擇出)遍歷數組,并記錄最后一次出現最多的元素e和相應的次數,如果下一個元素和e相同,則次數增加;如果不同,如果次數大于0,則次數減1;如果次數等于0,則將次數設置為1,且更新結果為當前元素

遍歷完成后,結果就是最后一次將次數置為1的元素。

3.數據流的中位數

中位數涉及中間的兩個數據,因此肯定要對數組進行排序。
我們可以將排序后前一半元素存儲在一個最大堆中,排序后的后一半元素存在在一個最小堆中,如果元素數量是偶數,則顯然取兩個堆的最大值和最小值取平均值即可;如果元素數量是奇數,我們將中間元素限定為包含在前一個堆中,因此中位數就是最大堆的的最大值。

由于數據流涉及更新,每新增一個數時,如果新增之前有偶數個元素,則將這個數加入到最大堆中;如果新增之前有奇數個元素,則將這個元素加入到最小堆中。

4.連續子數組的最大和

問題:求數組的連續子數組的和的最大值
:考慮A[i]和[i,j-1]的關系, 以A[i]結尾的連續子數組和不以A[i]結尾的連續子數組。因為連續子數組的性質就是,A[j]只能影響以A[j-1]結尾的子數組,因此其他的值都可以忽略。

f(j) 表示以A[j]結尾的最大連續子數組和
則 f(j) = max { f(j-1) + A[j], A[j] }
最終的最大值就是f(i)的最大值

5.數組中的逆序對數

問題:在數組中的A[i]和A[j] (i<j),如果A[i] > A[j],稱其為逆序。求數組中的逆序對數

:考慮[0,i]區間中小于A[i+1]的元素數量,就是A[i+1]逆序數量。
因此,考慮使用一個二叉搜索樹來確定元素的數量。

但是注意,手動實現一個平衡的二叉樹相當困難,因此,本題可以使用歸并排序的思想,歸并排序需要額外的空間來存儲合并的元素。

在分治的過程中,如果兩邊的數組已經排序完成,則統計逆序的十分方便。

6.剪繩子(切割問題)

題目:給定一段長為n的繩子,將其剪成m段,計算所有段段長的乘積,求最大值。
:考慮問題的第i段與第i-1段的關系,考慮第i段長為1的繩子片段,它可以選擇與 i-1合并,也可以不合并。
使用f(i)表示解決[0,i]的問題,則
選擇1:不與i-1合并
則第i段是游離的,長度為1,將此狀態記錄為g(i)表示第i段不與前i-1進行合并。由于i與i-1分離,所以繩子被分成兩段,變成子問題: f(i-1)*1

選擇2:與i-1進行合并
此狀態即為h(i), 則 h(i)可以轉化為f的問題
因為 i與i-1合并,所以末端的長度是2,此時剩余f(i-2)的問題,但是此時問題狀態還包括右側的長度,因此使用擴展f的狀態f(i,k)表示解決 [0,i]的問題,且右側還有k個長度
則h(i) = f(i-2,2)

由于擴展f后多出了k,所以需要對選擇1和選擇2重新考慮
若k==0,則選擇1和選擇2如上;若k>0, 則將k視為第i+1個元素即可

所以最終有:

f(i,k) = 若 k \== 0: max {f(i-1,0)*1, f(i-2,2) } 若k>0,則將k視為一個單獨的元素: max {不與i合并: f(i,0) * k與i合并: f(i-1,k+1) }

解2:上面的解法實際上過于復雜,實際上繩子一旦在某個點切斷,就是兩個獨立的子問題了,因此有 f(n) = max { f(n-i) * f(i) }

解3:貪心算法
實際上由于乘法的性質,當n>=5時,比如n=6或者n=12, 只需要將繩子分成3段一份即可,將得到3^k;當n<=4時,不用切割即是最大值

關鍵點:切割問題可以劃分為獨立的子問題進行簡單求解。

滑動窗口的最大值(單調棧)

問題:給定一個數組,大小為n,一個窗口m(m<=n), 在窗口滑動過程中會產生該區間內的最大值,求滑動產生的最大值。

:單調棧或者單調隊列是解決這類區間最值問題的關鍵。
我們考察窗口隊列中的最大值,因為隊列在向前移動,每次會刪除隊首元素,加入新的元素。
考慮下面的高度序列,每一個高度代表數字大小

| | | | | | | | | | | | | | | | | | | | 0 1 2 3 4 5 6

如果窗口為3,第1個窗口A[1]是最大值
第2個窗口,A[1]仍然是最大值
第3個窗口,A[1]被移除。剩下的點中A[2]不可能成為最大值,因為在A[2]的右側有大于A[2]的值存在,而[2,4]的窗口中最大值是 A[2]或者[2+1,4]的最大值。因此在比較過程中A[2]可以直接忽略掉。

更一般的說,在窗口[i,j]中,對于任意點k, 如果k的右側存在比k更大的數,則k可以從比較隊列中移除掉。

因此,算法的思想就是,維護一個待比較隊列,每次窗口移動,
對于新加入的元素,它將會淘汰掉所有比較隊列中小于等于它的元素。
因此,這個比較隊列從最先加入的元素到后加入的元素,實際上形成了一個嚴格單調遞減的序列
由于窗口移動需要移除區間最左側的元素,因此需要使用一個下標記錄最大值是否是隊首的值,如果是,則需要將這個值也移除掉。

window-max(A,m):Q = {}for i=0 to m-1:while Q is not empty and Q.tail > A[i]:Q.removeTail()Q.add(A[i])maxValues = {}k=0maxValues[k]=Q.frontfor i=m to A.size-1:# 最大值得下標是待移除元素的下標if Q.front.index == i - m:Q.removeFront()while Q is not empty and Q.tail > A[i]:Q.removeTail()Q.add(A[i])maxValues[k++] = Q.frontreturn maxValues

注意,也可以不必使用下標記錄,我們將比較隊列放寬為非嚴格單調遞減,則只需要在移除隊列是判斷隊首的值是否與待移除的值相等即可。如果隊列中相同元素較多,則這里會浪費較多時間比較。如果相同元素較少,則該方法更加節省空間。

單調棧和單調隊列:無論使用棧還是使用隊列,都可以完成上面的問題。當使用棧時,只需要棧能夠移除棧頂元素即可。相對而言,使用隊列更加容易理解刪除操作;使用棧更加容易理解比較過程。

問題2:選擇數組中的一個區間,使得區間內最小值與區間的和的乘機最大

:該問題需要搜索數組,搜索的過程中,以某個元素為最小值,則這個最小值的所在的區間的兩端肯定是比這個元素更小的值,因此我們可以使用單調隊列來查找該元素的區間。

我們維護一個遞增的單調序列,當遇到小于隊尾(最新的元素)時,則該元素的區間就確定了(它的前一個元素與這個元素之間的區間),因此將這個元素從隊尾移除。如此循環,可以遍歷完所有的組合。復雜度:O(n)。

注意:實際上由于不需要對隊首進行操作,因此使用單調棧更加簡單。

問題3: 柱狀圖的最大矩形

問題4:柱狀圖的最大容納面積

單調棧

單調棧解決問題使用的數據結構:一個隊列,問題的狀態包括已經出隊的元素
單調棧的循環不變式:所有不在隊列的元素已經解決

3.約瑟夫問題

題目:把0到n-1這n個數字排成一個圓圈,從數字0開始每次依次刪除第m個數字,最后剩余的是哪一個數字?

遞推公式:f(n,m) = [f(n-1,m)+m]%n (n>1), 如果n==1,則f(n,m)=0.

7.股票的最大利潤

題目:把某支股票的價格按照時間順序存儲在數組中,買入和賣出該股票的最大價值是多少?比如 {9,11,8,5,7,12,16,14} 買入5,賣出16,利潤11最大。
:只需要從數組的最左側選擇一個最小的數,然后從數組的最右側選擇一個最大的數,兩者相減即是最大利潤。
如何劃分這些解呢?每一組解必然包含一個中間界限i,[0,i]的最小值和(i,n-1]的最大值,就是以i為界限的一個解。中間界限有 n-2個,所有只需要選擇n-2個解里面得最大值即可。

簡化:選擇界限其實是存在重復的,以i為界限,如果A[i+1]比A[i]大,但是小于右側最大值,則存在重復。
考慮以i為賣出點的最大值,顯然只需要知道[0,i)的最小值即可求出。共有n-1個可行解,選擇最大值即可。

9.遞增數組中,求和為s的一對數

考慮f(i,j)表示 [i,j]內的解,考慮以A[i],A[j]作為其中一個解,則A[i] + A[j]是最大的那個;如果A[i]+A[j]=s,則成為解;如果A[i]+A[j] < s,則A[i]不可能是解,轉化到f(i+1,j);如果A[i] +A[j] > s, 則A[j]不可能是解,轉化到f(i,j-1)

即:

f(i,j) = [i,j] 如果A[i]+A[j] == s
f(i,j) = f(i+1,j) 如果A[i]+A[j] < s
f(i,j) = f(i,j-1) 如果A[i]+ A[j] > s

10.數組區間的最小值與和的乘積最大值

關鍵點:考慮數組的分區,以小于某個數可分成多個分區。
題目:給定一個數組,求一個區間[i,j]使得sum(i,j) * min(i,j) 具有最大值。
注:數組的每個元素保證在[0,100]范圍內

解1:注意此題的一個顯著的條件,元素值范圍是[0,100]
我們考慮從0到100遍歷即可。從解的角度考慮,我們需要一個最小值,然后在保證最小值的情況下求最大的和,然后求乘積。
從0到100依次假設最小值j,然后遍歷數組A,每當遇到比j小的值時,結束一個區間的計算;直到遍歷完所有的最小值,最終得到最大計算結果。

以j為最小值的視角: [A0,A1, … , j-1) [Ax, Ay, …,j, j-2)
可以看到這些區間實際上都是以小于j的值作為分界點的。

復雜度:O(100*N)

解2:遍歷數組時,使用一個棧記錄區間狀態,棧頂元素是當前最小值的起始下標。如果當前元素比棧頂元素小,說明 [棧頂元素,當前元素) 構成了一個區間,此時可得一個候選解,然后將棧頂元素彈出,直到當前元素比棧頂元素大。這樣可以保證棧頂元素到當前元素是以當前元素為最小值的最大區間。
關鍵點:使用單調棧形成隔離區間
區間性質:以 A[i]為最小值的最大區間中Zi,如果A[j]屬于Zi且A[i]<A[j], 則A[j]的最大區間Zj是Zi的子區間。 因此,可以使用棧來記錄嵌套區間,當子區間求完之后,父區間可繼續延伸。

例如:5 9 6 8 7
棧的變化:
0: 5
1: 5 9
2: 5 6 彈出區間 [1,2)
3: 5 6 8
4: 5 6 7 彈出區間 [3,4)
5: 最后 彈出所有剩余區間 [4,5), [2,5), [0,5)

其結果就是:以A[i]為最小值的所有最大區間都能確定下來,從而能夠確定 sum(i,j) * min(i,j)

復雜度:O(N)

11.移除數組連續元素獲得的最大值

鏈接:https://leetcode.com/problems/remove-boxes/
不斷從一個正數數組中移除k個相同的數,每一次移除獲得k*k個價值,求最后將數組移除完畢后能夠獲得的最大價值。
示例:

[1, 3, 2, 2, 2, 3, 4, 3, 1]
----> [1, 3, 3, 4, 3, 1] (33=9 points)
----> [1, 3, 3, 3, 1] (11=1 points)
----> [1, 1] (33=9 points)
----> [] (22=4 points)
最終獲得23

:考慮問題的空間狀態,如果使用一個二進制數反映數組的元素是否存在,則問題狀態圖中,共有2^n個節點,節點之間的路徑就是移除數目的平方,問題轉化為求解從起始狀態到終止狀態的最大路徑。
考慮對同一個元素而言,如果從一個數組中1個,和移除多個,顯然一次移除多個的價值是最大的(因為 (a+b)^2 > a^2 + b^2), 所以每次可從數組中移除一組相鄰的元素。
考慮移除第i組然后移除第i+1組元素,和先移除第i+1組,然后移除第i組,最終問題都轉化成前i-1組的子問題,因此存在子問題重疊。
考慮f(i,j)表示移除[i,j]之間元素的最大值,則
若設r[j]為A[j]在[i,j-1]之間出現的所有位置。則A[j]可選擇與r[j]中的任意元素進行進行合并。
由于合并后,末尾元素的數量會累積,所以使用g(i,j,s)表示求解[i,j]且A[j]數量為s的解;顯然有
f(i,j) = max{ g(i,r,C[j] + C[r]) + f(r+1,j-1) }, r為[i,j-1]中所有與A[j]相同的下標
g(i,j,s) = max{ g(i,r,C[r]+C[r]) + f(r+1,j-1) }, r為[i,j-1]中所有與A[j]相同的下標

cache = new int[n][n]{ INF }; f(i,j):if i==j:return 1if cache[i][j] set:return cache[i][j]max = f(i,j-1) + 1for r=i to j-1:if A[r] == A[j]:sum = g(i,r,C[r] + C[j]) + f(r+1,j-1)if max < sum:max = sumreturn max g(i,j,s):if i==j:return s*smax = f(i,j-1) + s*sfor r=i to j-1:if A[r] == A[j]:sum = g(i,r,C[r] + s) + f(r+1,j-1)if max < sum:max = sumreturn max

解2:上面的解法實際上是存在冗余的,因為每次都需要試圖匹配一個點,導致算法需要查找所有的點,如果我們將子問題改成,與左側區間的一個合適的值匹配,而不必循環即可解決;則子問題1.不與左側區間任何一個值匹配 2.與左側第一個值匹配 3.與左側區間的第一個值的左側區間匹配

我們將普通問題表述為f(i,j), 即[i,j]的最大值,則問題2可使用h表述:

由于問題2產生匹配時,會累積邊界的值,因此h需要3個參數,即h(i,j,m)
則 h(i,j,m) = h(i,r,m+1) + f(r+1,j-1)
表示子問題[i,r]和[r+1,j-1]
其中r為j的左鄰接點

問題3假定使用g來表示,r表示j的左側第一個鄰接點;我們發現問題3實際上具有遞歸結構,因為左側區間的左側區間仍然構成一個子問題,則

g(i,j,r,m) = max { f(i,r,m+1,m) + f(r+1,j-1,m), g(i,j,r’,m) }
表示子問題由于左鄰接點匹配的結果與左鄰接點的左側匹配的結果的最大值

g的示例圖:

[i, ... ,r', r, ... ,j] j與r匹配 或者j與r左側的點匹配,即j與r'或者r'的左側的點匹配,具有問題g的結構 # prev[r]表示r的前驅下標 # 最終結果是 f(0,n-1,1) f(i,j,m):if i>j:return 0if i==j:return m*mreturn max(f(i,j-1,1) + m*m, h(i,j,m), g(i,j,prev[j],m)) h(i,j,m):if i>j:return 0if i==j:return m*mreturn prev[j]<i ? 0 : f(i,prev[j],m+1) + f(prev[j]+1,j-1,1) g(i,j,r,m):if i>j || r<i:return 0return max(f(i, r, m + 1) + f(r + 1, j - 1, 1), g(i, j, prev[r], m))

注:上面的代碼不經過優化,即使對f,h做緩存,運行時間仍然是204ms,離最優時間3ms相差甚遠。
關鍵點:問題分類討論,使用不同的符號表示不同的子問題。如果可能,合并不同符號減小子問題數量。

優化1:嘗試合并子問題,第一步,去掉函數h,因為h只是單純調用f。
優化后時間:160ms, 減少了40ms

優化2:使用g代表通用的轉化形式
實際上,我們發現g才是函數的通用形態,f可以化成g的形式。令g能夠解決點j不與[i,j]中的任何一個點合并的問題,然后將問題降低到二維并進行可能的剪枝。
當f全部化成g的形式調用之后,f不再需要m參數,因此緩存的空間降低一階。

// dp[n][n]記錄解 f(i,j):if i>j:return 0if i==j:return 1if dp[i][j] set:return dp[i][j]return dp[i][j]=g(i,j,prev[j],1) g(i,j,r,m):if i>j:return 0if r<i:return f(i,j-1)+m*mif r+1==j:return g(i,r,prev[r],m+1)return max{g(i,r,prev[r],m+1) + f(r+1,j-1), g(i,j,prev[r],m)}

上面的解最終的運行時間是2ms, 超過了100% 的解法。而其中最重要的一行代碼就是

if r+1 == j:return g(i,r,prev[r],m+1,sum)

這一步消除了許多無用的中間狀態,達到剪枝的目的。
最終提交的java代碼:

class Solution {int n;int[][] F;int[] prev;public int removeBoxes(int[] boxes) {n = boxes.length;prev = new int[n];F = new int[n][n];for (int i = 0; i < n; i++) {prev[i] = -1;for (int j = 0; j < n; j++) {F[i][j] = -1;}for (int j = i - 1; j >= 0; --j) {if (boxes[i] == boxes[j]) {prev[i] = j;break;}}}return f(0, n - 1);}public int f(int i, int j) {if (i > j) {return 0;}if (F[i][j] != -1) {return F[i][j];}if (i == j) {return F[i][j] = 1;}return F[i][j] = g(i, j, prev[j], 1);}public int g(int i, int j, int r, int m) {if (i > j) {return 0;}if (r < i) {return f(i, j - 1) + m * m;}// 如果相鄰,可以消除許多中間狀態if (r + 1 == j) {return g(i, r, prev[r], m + 1);}int rv = prev[r];return Math.max(g(i, r, rv, m + 1) + f(r + 1, j - 1),g(i, j, rv, m));} }

(此問題我抓狂了一天一夜 --2020年02月13日)

我之前的解析(2017年寫的,稍微有點復雜):https://leetcode.com/problems/remove-boxes/discuss/101320/a-dp-solution-an-extra-thought-of-using-dancing-links/408318

12.判斷數組中的元素是否只出現了一次

排序,然后統計。

如果要求盡量少的空間復雜度,我們我們將遞歸也考慮進去,則要排除快速排序。
所以,最終只有堆排序能夠在O(1)的空間要求下完成O(NlogN)的時間復雜度。

13.選擇小于k的數中最大的價值

題目:給定一個二維數組A, A[i][0] 表示選擇i所需的最小值,A[i][1]表示選擇i獲得價值,給定值k,求能夠獲得最大價值。

鏈接:https://www.nowcoder.com/practice/46e837a4ea9144f5ad2021658cb54c4d?tpId=98&tqId=32824&tPage=1&rp=1&ru=/ta/2019test&qru=/ta/2019test/question-ranking

此題粗想不難,但是有許多細節值得注意。
首先,我們對數組按照A[i][0]進行升序排序,然后找到k在數組中的最接近的位置r,返回排序后小于等于r的最大價值。

為此,我們需要實現二分查找來獲得k的下標,然后使用一個最大值數組記錄區間最大值。

14.序列轉換的次數

題目:已知一個奇怪的隊列,這個隊列中有n個數,初始狀態時,順序是1,2,3,4,…n,是1-n按順序排列。這個隊列只支持一種操作,就是把隊列中的第i號元素提前到隊首(1<i<=n),如有4個元素,初始為1,2,3,4,可以將3提前到隊首,得到3,1,2,4 。 現在給出一個經過若干次操作之后的序列,請你找出這個序列至少是由原序列操作了多少次得到的。

鏈接:https://www.nowcoder.com/practice/02f2bbaadbcf424b8cbc7e264ddef9b4?tpId=98&tqId=33056&rp=1&ru=%2Fta%2F2019test&qru=%2Fta%2F2019test%2Fquestion-ranking&tPage=12

考慮序列[1,2,3,4,5], 它的下一個狀態如下:
[2,1,3,4,5], [3 1 2 4 5], [4 1 2 3 5],[5 1 2 3 4]
不考慮狀態的回退,這就意味著,后續的元素只能選擇從右側的單調遞增區間中選擇一個元素來變換,從而,我們可知,數組末端最長遞增的序列就是所有無需調整的元素,而左側的元素則是需要調整的。

因此,解就是統計數組末端的最長遞增序列長度s,則需要調整n-s次。

15. k-sum

題目

圖論

1.BFS和DFS

BFS的性質:在一個所有距離都為1的圖中,能夠找到任意兩個點之間的最短路徑。因為BFS的性質就是擴展。這個性質在格子地圖的搜索中使用比較多,比如推箱子,迷宮問題。

BFS
廣度優先搜索需要選擇一個起點,其結果是該點能夠到大的所有最終節點v,且形成的路徑具有最少的邊。
廣度的含義:該算法僅在遍歷完其距離為k的節點時,才會遍歷距離為k+1的節點。
初始時,所有的節點標記為未訪問;起點標記為已訪問。維護一個隊列表示將要訪問的隊列
偽代碼

BFS(G,s):for v in G:visited[v]=falsed[v] = -1Q = {s}d[s] = 0visited[s] = truewhile Q is not empty:u = Q.poll()for v in G.adj[u]:if not visited[v]:d[v] = d[u]+1visited[v]=trueQ.add(v)

注意visited設置的時機,應當是在加入到隊列之后就設置,不然可能存在重復入隊的問題。

DFS: DFS對圖進行深度優先遍歷

DFS(G):for u in G:visited[u]=falsed[u]=0t=0for u in G:if not visited[u]:DFS-VISIT(G,u) DFS-VISIT(G,u):d[u]=t++visited[u]=truefor v in G.adj[u]:if not visited[v]:DFS-VISIT(G,v)

遍歷完成后,d將保存每一個節點遍歷的先后順序。

2.拓撲排序和最短路徑

拓撲排序:是有向無環圖的一個線性次序,所有的有向邊都指向右側。

排序算法:

topo-sort(G):dfs(G) with when each u is finished, insert u to front of listreturn list

如果存在環,則拓撲排序結果無效。

有向無環圖的單源最短路徑:對圖的拓撲順序進行遍歷,我們可以知道排在前面的點不存在其他可能的路徑,因此對其進行松弛后就是最短路徑。
因為G.adj[u]只能是在拓撲順序的后面位置

dag-shortest-path(G,w,s):list = topo-sort(G)init-single-source(G,s)for u in list:for v in G.adj[u]:relax(u,v,w)

正確性證明:對于圖中的所有節點v,s到v的最短路徑為 (v0,v1,…,vi) ,v0=s, vi=v; 則路徑仍然是拓撲排序的,所以其松弛的次序為 (v0,v1), (v1,v2), … (vi-1,vi). 由于 (v0,v1)松弛后, v1不存在其他的可選路徑,所以(v0,v1)是一條最短路徑,同理 (v0,v1,v2)也是一條最短路徑, … (v0,v1,…,vi)是一條最短路徑
很顯然,拓撲排序中在s前面的節點距離都是0,只有在s后面的節點才具有最短路徑。

最短路徑中的松弛性質:如果p=(v0,…vi)是一條最短路徑,則只要保證松弛順序是 (v0,v1), (v1,v2) … (vi-1,vi),則d(v0,vk)是v0到vk的最短路徑。

3.Bellman-Ford算法求最短路徑

解決一般情況下的單源單源路徑問題,權重可以為負值。
Bellman-Ford算法的核心是最短路徑松弛性質,設s到v的所有可能的無環路徑為p0,p1,…pi, 則pi的最大長度是|G.V|. Bellman-Ford算法首先找到長度小于等于0的最短路徑,也就是s本身;然后找到長度小于等于1的最短路徑,依次找到長度小于等于|G.V|的最短路徑。最終,得到路徑距離。
但是注意,如果路徑中存在負環,則最短路徑是不存在的,這種情況下,只需要最后測試是否還能夠對路徑進行松弛,如果不能則找到最短路徑;如果可以則存在負環

bellman-ford(G,w,s):init-single-source(G,s)for i=1 to |G.V|-1:for (u,v) in G.E:relax(u,v,w)for (u,v) in G.E:if can-relax(u,v,w):return FALSEreturn TRUE

算法分析:復雜度 O(VE)
由于算法無差別地循環V-1次,每次需要遍歷每條邊,所以相對于Dijkstra算法而言,其效率更差。

SPFA 優化的Bellman-Ford算法

SPFA wiki
該算法使用一個隊列保存所有產生了更新的節點,算法的每一步是從隊列中取出一個點,然后更新其鄰接點。
該算法的最壞情況和Bellman-Ford算法一樣,O(VE).

spfa(G,w,s):init-single-source(G,s)Q = {s}d[s] = 0inQueue = {}inQueue[s] = truewhile Q is not empty:u = Q.poll()for v in G.adj[u]:if relax(u,v,w):if not inQueue[v]:inQueue[v] = trueQ.add(v)inQueue[u] = false

如果存在負環,則算法永遠不會終止。因此,需要保證圖G沒有負環。
實際上,一個點加入到隊列中最多等于它的鄰接點個數,如果超過這個次數,說明該點存在負環。

4.Dijkstra最短路徑

關鍵點:每次使用優先隊列選擇最短的路徑進行松弛,松弛過程中還會調整優先隊列
求解有向圖非負權重的單源最短路徑問題。
注:圖的表示,V表示所有的點, G.adj[u]表示u的所有鄰接點
偽代碼:

# G圖, G.w權重函數 dijkstra(G,s):init-single-source(G,s)Q = G.V S = {}while Q not empty:u = extract-min(Q)S = S + {v}for v in G.adj[u]:relax(u,v,G.w)# 初始化d, parent屬性 init-single-source(G,s):for v in G.V:v.d = infitev.parent = NILs.d = 0# 更新v的路徑和parent,u作為起點 # 由于這里要使用v.d,所以狀態v是復用的,在某些情況下(比如推箱子),v需要使用一個map來記錄d relax(u,v,w):if v.d > u.d + w(u,v):v.d = w.d + w(u,v)v.parent = u

算法中優先隊列有兩個操作: 1.extract-min操作 2.decrease-key操作(即將路徑松弛后,調整元素在隊列中的位置)
復雜度取決于優先隊列的實現:
1.如果優先隊列使用平常的數組,則每次查找最大值都需要O(V)時間,總的復雜度是O(V^2).
2.如果是稀疏圖,可以使用二叉堆來實現優先隊列,extract-min=O(logV),decrease-key=O(logV), 總的運行時間 O((V+E)logV)
3.使用斐波那契堆,extract-min和decrease-key的攤還操作是O(logV),總復雜度是O(VlogV + E). 實際上,斐波那契堆的改進就來源于迪杰斯特拉算法中decrease-key操作遠比extract-min操作頻繁而來。

使用數組編寫

public class Dijkstra{public int dijkstra(int[][] G,int s){int n = G.length;int[] d = new int[n];// 使用done[]標記已經完成的點boolean[] done = new boolean[n];for(int i=0;i<n;++i){d[i]=-1;}d[s] = 0;done[s]=true;for(int i=0;i<n;++i){// extract-min操作int min = -1;for(int j=0;j<n;++j){if(!done[j] &&d[j]!=-1&& (min==-1 || d[j]<d[min])){min = j;}}done[min]=true;for(int k=0;k<n;++k){if(G[min][k]>0){// relaxif(d[k]==-1 || d[k] > d[min] + G[min][k]){d[k] = S[min] + G[min][k];}}}}} }

關鍵點:使用done數組標記已經完成的點避免重復查找;extract-min操作需要排除已經完成的點

2.TSP旅行商問題(TSP)

題目:有n個城市,任意兩個城市之間距離為d[i,j]。如果希望從城市0出發走遍每一個城市且每個城市僅路過一次,最后回到城市0,求最小路徑。
:該問題實際上可以抽象為下面的子問題:設當前處于i城市,未訪問的城市集合是V,求從i出發,經過V的所有節點,返回城市0的最小路徑。
記為f(i,V), 則

f(i,V) = min{ div + f(v,V-{v}) }, 當V為空時, f(i,V) = di0

限定V的數量為20個,則可以使用一個int類型和二進制來記錄。
使用數組記錄,內存在M級別。

強連通分量(component)

定義:在有向圖中,強連通分量是指圖的一個子圖,該子圖中,任意兩個節點都能夠相互到達。

可以將圖拆分成若干個強連通分量,將這些連通分量視為一個點,圖就收縮為一個有向無環圖。

算法:對于圖G, G’表示G的轉置,也就是將G中的每一邊都反轉。

STRONGEST-CONNECTED-COMPONENT(G):dfs(G), get u.f for each vertex u in Gdfs(G') in decreasing order of u.f:then each tree in dfs(G') is a strongest-connected-component

第一步DFS先找到所有點的拓撲排序,然后第二步DFS根據拓撲排序的倒序結果,尋找所有點的起點,最終獲得連通分量。

3.最小生成樹(MST)

定義:在一個無環圖中,尋找一條線將圖中的所有點都連接起來,并且權重和最小。
因為這些點連起來以后必然形成一棵樹(沒有回邊),從而將這個這棵樹稱為最小生成樹。

通用MST算法:

GENERIC-MST(G,w):A = {}while A is not a spanning tree:find and edge (u,v) that is safe for AA.add({u,v})return A

循環不變式:每次循環開始時,A是某棵最小生成樹的子集

因為每次循環只加入了一條安全邊,所以加入的安全邊仍然構成了一個最小生成樹。算法的關鍵是尋找一條安全邊。

Kruskal算法和Prim算法符合該基本算法。
Kruskal算法:維護一個生成樹的集合,每次選擇連接兩棵生成樹的最小邊,然和合并這兩棵樹。
使用并查集:

MST-KRUSKAL(G,w):A = {}for v in G.V:MAKE-SET(v)sort G.E in non-decreasing orderfor (u,v) in G.E taken in non-decreasing order:if FIND-SET(v) != FIND-SET(u):A.add({u,v})UNION(u,v)return A

Prim算法:從某個節點開始生成一顆最小生成樹A,維護一個尚未加入A中的集合Q,Q的所有點記錄了它們與A連接的最小值。每次循環時,從Q中取出連接權重最小的那個,然后更新其他Q中相鄰的點的權重。

該算法和dijkstra算法類似,每次從未解決的點中選擇一條最小的邊加入最小路徑,然后更新相鄰的點。

MST-PRIM(G,w,r):for v in G.V:u.key = INFu.parent = NILr.key = 0Q=G.Vwhile Q is not empty:u = EXTRACT-MIN(Q)for v in G.adj[u]:if v in Q and w(u,v) < v.key:v.parent = uv.key = w(u,v)

不能存在負向環

算法

1.一系列數據中有一個數出現了k次,求這個數

2.并查集

并查集可以快速判斷兩個

關鍵點:1.支持union和find兩個操作 2.每個點使用屬性p表示歸屬的集合,根節點的p屬性等于其自身
偽代碼

makeSet(x):x.p = xfind(x):while x!=x.p:x=x.preturn xunion(x,y):find(x).p = find(y)

帶有秩優化的find操作:

find(x):if(x.p==x)return x;return x.p=find(x);

矩陣中的并查集問題(TODO 提交)

題目:778. Swim in Rising Water

在一個矩陣中G中, G[i][j]表示該點的海拔,在t時刻下雨,G[i][j]的高度是 max{t, G[i][j]}. 現在你處于G[0][0],你可以在0時刻內游到任何高度不超過t的格子中, 經過最少多少時間后可以游到G[n-1][n-1]?

:判斷在給定t時間內,矩陣是否能夠從G[0][0]到大G[n-1][n-1],由于t的最小值是1,最大值是N^2,所以使用二分查找來確定t的最小值。

Number of Islands

題目:200. Number of Islands(Medium)

給定一個矩陣G[n][m], G[i][j]=1表示島嶼,G[i][j]=0表示水,求島嶼數目

:此題可使用傳統的并查集來解決,如下:

class Solution {int[] prev;int c;int find(int x){if(x==prev[x])return x;return prev[x]=find(prev[x]);}void union(int x,int y){int px=find(x);int py=find(y);if(px!=py){--c;prev[px]=py;}}public int numIslands(char[][] grid) {int n=grid.length;if(n==0){return 0;}int m =grid[0].length;prev = new int[m*n];c=0;for(int i=0;i<n;++i){for(int j=0;j<m;++j){prev[i*m+j] = i*m+j; if(grid[i][j]=='1'){++c;}}}for(int i=0;i<n;++i){for(int j=0;j<m;++j){if(grid[i][j]=='1'){if(i-1>=0 && grid[i-1][j]=='1'){union(i*m+j,(i-1)*m+j);}if(j-1>=0 && grid[i][j-1]=='1'){union(i*m+j,i*m+j-1);}}}}return c;} }

但是更快的方法是直接在數組中修改集合關系,每當我們遇到1時,我們就清除周圍的1,直到結束。因為1是連續的,我們往四個方向清除之后,保證這個集合的1不會再被訪問,因此每次遇到1都是集合數量。這種方法的復雜度是O(NM).

public class Solution {public int numIslands(char[][] grid) {int count = 0;for (int i = 0; i < grid.length; i++) for (int j = 0; j < grid[i].length; j++)if (grid[i][j] == '1') {count++;clearRestOfLand(grid, i, j);}return count;}private void clearRestOfLand(char[][] grid, int i, int j) {if (i < 0 || j < 0 || i >= grid.length || j >= grid[i].length || grid[i][j] == '0') return;grid[i][j] = '0';clearRestOfLand(grid, i+1, j);clearRestOfLand(grid, i-1, j);clearRestOfLand(grid, i, j+1);clearRestOfLand(grid, i, j-1);} }

遞歸解法還可用于此題: 695. Max Area of Island(Medium)

class Solution {int[][] grid;public int maxAreaOfIsland(int[][] grid) {this.grid = grid;int max = 0;for(int i=0;i<grid.length;++i){for(int j=0;j<grid[i].length;++j){if(grid[i][j]==1){max = Math.max(findClearAll(i,j),max);}}}return max;}int findClearAll(int i,int j){if(i<0||i>=grid.length||j<0||j>=grid[i].length||grid[i][j]==0)return 0;grid[i][j]=0;return 1+findClearAll(i-1,j) + findClearAll(i+1,j) + findClearAll(i,j-1) + findClearAll(i,j+1);} }

Friend Cycle

題目: 547. Friend Circles(Medium)

給定一個關系矩陣G[n][n], G[i][j]=1表示i和j存在朋友關系, 如果i,j存在朋友關系, j,k存在朋友關系,則i,k存在間接朋友關系。請找出所有的朋友圈,每個朋友圈中的兩個人要么是朋友關系,要么是間接朋友關系

:這道題說白了還是考察并查集的內容,因為i要在一個朋友圈中,i必然與朋友圈中的一個人是直接朋友關系,所以只需要把所有具有朋友關系的人并在一起即可。

實際上,間接朋友關系對本題沒有任何影響。

class Solution {int[] prev;int c;int find(int x){if(prev[x]==x)return x;return prev[x]=find(prev[x]);}void union(int x,int y){int px=find(x);int py=find(y);if(px!=py){--c;prev[px]=py;}}public int findCircleNum(int[][] M) {// in a cycle, person A must have a direct friend.// c=N// for each person A// makeSet(A)// for each person A// for each friend B:// if A,B not the same// union(A,B)// --c// return cint n=M.length;prev = new int[n];c=n;for(int i=0;i<n;++i){prev[i] = i;}for(int i=0;i<n;++i){for(int j=i+1;j<n;++j){if(M[i][j]==1){union(i,j);}}}return c;} }

3.整數的平方

4.背包問題

題目1: 給定體積下的放置方式 鏈接

給定一個背包容量為w, 共有n個物品,每個物品的體積是v[i],求總體積不超過w的情況下,有多少種放置物品的方法?(體積為0也是一種方法)

:f(A,w) = sum { f(A - {i}, w - v[i]) + f(A -{i},w) }
由于子問題無論如何都會去掉i,因此我們可以將問題簡化為求 [i,n-1]的區間的背包問題
f(i,w) = f(i+1, w-v[i]) + f(i+1,w)

緩存使用map記錄w對應的值。

回溯法

0.一般應用背景

棋盤,或者數組規模小于等于9時

1.八皇后問題:棋盤上八個皇后的布局

對每一個格子(x,y), 如果該點被占用,則上它標記了縱線,正斜線和反斜線三條線上的格子不能用
因此進入下一層時所有的點只需要判斷它的縱線,正斜線和反斜線是否未被占用即可。

計算(x,y)的正斜線和反斜線的位置

| |\ | \ |_ _ _ _ _ _ _ _ _

左斜線直線組: y = -x + T, T從0到2*(n-1)
右斜線直線組:y = x + T, T從-(n-1)到(n-1)

所以,左斜線的序號: y+x, 右斜線的序號:y-x + (n-1)

注意事項:1.代碼中以int search(i)為原型,表示當前狀態下搜索第i層產生的解,注意i==n時返回1

代碼:

vertical = new boolean[n]; left = new boolean[2*n - 1]; right = new boolean[2*n - 1];public static int search(int i){if(i==n){return 1;}int count = 0;for (int j = 0; j < n; j++) {if(vertical[j] || left[i+j] || right[i - j + n - 1]){continue;}vertical[j] = left[i+j] = right[i-j +n-1] = true;count += search(i+1);vertical[j] = left[i+j] = right[i-j +n-1] = false;}return count; }

2.迷宮問題

題目:給定一個二維數組,其中0表示通道,1表示圍墻,指定起點和終點,尋找一條通道。
:路徑是不存在重復的,因此如果一個點已經走過,它不可能重新成為路徑的一部分。所以搜索時需要將路徑上的點標記為已訪問。
當前點如果不是終點,則將鄰接的可用的點加入到棧中,依次進行搜索。

剪枝:如果一個點已經走過,則它的兩個相鄰點只能有一個成為解的一部分。因為如果兩個點都在路徑上,則選擇路徑靠后的一個,前面的點就被消除了。所以在回溯時,無需考慮將鄰接點的狀態從已用轉化為可用。因此下面的代碼中,首先將所有的可用鄰接點標記收集起來,然后標記為已用,再進行回溯。這樣能夠避免路徑上出現同一個點的兩個鄰接點。非常重要的一點是:在鄰接點遍歷完畢后,應當將所有的點重新標記為可用,因為它們只是在鄰接點期間不可用。

偽代碼:

# t 終點 solution = {s} search(s):if s==t:print(solution)returnavlAdj = []for q in s.adj:if q.used==0:continueq.used = 1avlAdj.add(q)for q in avlAdj:solution.add(q)search(q)solution.removeLast()# 為了找出所有的解,這里需要進行恢復for q in avlAdj:q.used = 0

java代碼,打印所有的路徑

import java.util.ArrayList; import java.util.List; import java.util.Scanner;public class Solution {private static int n;private static int m;private static int[][] arr;private static int[] start;private static int[] end;public static void main(String[] args) {Scanner scanner = new Scanner(System.in);n = scanner.nextInt();m = scanner.nextInt();start = new int[] { scanner.nextInt(),scanner.nextInt()};end = new int[] { scanner.nextInt(),scanner.nextInt()};arr = new int[n][m];for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {arr[i][j] = scanner.nextInt();}}/*5 50 44 40 0 1 0 00 0 0 0 00 0 0 1 01 1 0 1 10 0 0 0 0*/// [down, left, left, down, down, down, right, right]// [left, down, left, down, down, down, right, right]// trueSolution solution = new Solution();System.out.println(solution.hasPath(arr,start,end));}class Pack{int[] data;String dir;public Pack(int x,int y,String dir) {this.data = new int[]{x,y};this.dir = dir;}}List<String> solution = new ArrayList<>();public boolean hasPath(int[][] maze, int[] start, int[] destination) {if(start[0]==destination[0] && start[1]==destination[1]){System.out.println(solution);return true;}List<Pack> points = new ArrayList<>(4);if (start[0]>0 && maze[start[0]-1][start[1]]==0){points.add(new Pack(start[0]-1, start[1],"up"));}if(start[0]<maze.length-1 && maze[start[0]+1][start[1]]==0){points.add(new Pack(start[0]+1, start[1],"down"));}if(start[1]>0 && maze[start[0]][start[1]-1]==0){points.add(new Pack(start[0], start[1]-1,"left"));}if(start[1]<maze[0].length-1 && maze[start[0]][start[1]+1]==0){points.add(new Pack(start[0], start[1]+1,"right"));}for (Pack point : points) {//visitedmaze[point.data[0]][point.data[1]]=1;}boolean once = false;for (Pack point : points) {solution.add(point.dir);once |= hasPath(maze,point.data,destination);solution.remove(solution.size()-1);}for (Pack point : points) {maze[point.data[0]][point.data[1]]=0;}return once;} }

題目鏈接
https://leetcode.com/articles/the-maze/

https://leetcode-cn.com/problems/word-search/?utm_source=LCUS&utm_medium=ip_redirect_q_uns&utm_campaign=transfer2china

3.推箱子

鏈接:https://leetcode.com/problems/minimum-moves-to-move-a-box-to-their-target-location/
題目:在一個NxN的地圖上,有一個箱子,一個目的地,一個人,以及一些障礙物,計算出最少需要多少步將,人能夠將箱子推到目的地。
解1:該問題實際上比迷宮的問題復雜。將人和箱子的坐標視為狀態 (xp,yp), (xb,yb),則最終狀態就是xb=終點坐標,yb=終點坐標。從圖論的角度來看,節點與節點之間的距離是0或者1,而且有多個終點。可以使用dijkstra算法求解最短路徑,然后選擇其中箱子位置與目標位置相同的節點的最小值即可。實際上如果是求最短路徑,則第一個出現的狀態就是目標狀態。

基于Dijkstra算法,如果使用數組則需要遍歷很多無用狀態,因此需要使用隊列。隊列中存儲狀態,不存儲解,解使用其他的map存儲。
偽代碼:

search(p,b,t):Q = {}disMap = {}moveStep = {}visited = {}parent = {}initState = {player:p, box:b}Q.add(initState)disMap[initState]=0visited[initState]=truewhile !Q.empty():u = Q.poll()visited[u]=trueif u.box == t:printSolution()returnfor v in move(u):if disMap[v.state] == NIL || disMap[v.state] > disMap[u] + v.delta:moveStep[v.state] = v.nameparent[v.state] = uQ.remove(v.state)disMap[v.state] = disMap[u]+v.deltaQ.add(v.state) move(u):for i in u.adj:# 這里必須排除那些已經訪問的點if visited[i]:continue返回所有相鄰節點,每個節點包含 state={player:,box:}, delta=移動步數, name=移動名稱

注意:必須使用visited限定move的節點。

分析:dijkstra算法使用數組實現會比使用普通隊列慢,但是復雜度增長是一樣的; 因此在推箱子的問題中,雖然dijkstra算法易于實現,但是效果不佳;除非能夠使用斐波那契數堆。

解2:優化的Dijkstra算法
因為我們知道所有可能的路徑介于0到n*m之間(路徑最多包含所有的格子),因此在記錄最小路徑時,可以使用一個Tree來存儲路徑i的所有狀態。則每次從Tree中取出最小的路徑i以及對應的點集Q,則點集Q的所有點的路徑都已經確定了,將它們加入到visited中

對點集Q的每一個相鄰點P,如果P不在visited中,則進行下面的步驟:
因為相鄰點的距離只有0和1,如果是0,則將P加入visited中;如果是1,將P放到Tree的路徑為i+1的集合中

所以,實際上整個問題變成:尋找最短路徑=i (0 <= i <= n*m)對應的所有點集。
由于最短路徑的下一個狀態要么是0,要么是1,所以只需要維護一個集合即可,不需要visited等狀態。

search(p,b,t):for each state:inQueueOrSolved[state]=falseQ = { (p,b) }inQueueOrSolved[(p,b)]=true# 當前路徑i = 0while Q is not empty:N = {}for u in Q:if u==t:return ifor (v,s) in move(u):inQueueOrSolved[v]=trueif s==0:Q.add(v)else:N.add(v)++iQ = Nmove(u):需要排除inQueueOrSolved的狀態

循環不變式:queue中保存所有已解決的路徑為i的點
所有可能移動的點可能是:前一層已訪問的點(忽略),同一層已訪問的點(忽略),同一層未訪問的點,加入到當前層中;下一層未訪問的點,加入到下一層。

注意,當我們將一個點加入到下一層時,實際上就已經決定了它的距離至少是i+1。那么,是否存在一個點違反這個性質呢?由于隊列中所有的狀態都是唯一的,因此所有狀態的能夠推動箱子的狀態是唯一的。

解3:對狀態圖進行BFS,并使用隊列存儲產生更新的點
循環不變式:每次循環時,隊列中包含所有已產生距離更新的節點
為了避免重復更新,同一個節點不能被加入隊列中兩次,所以需要一個狀態記錄是否在隊列中(是否產生了更新)
當到達t節點后,無需將t節點放入隊列中,因為如果其他節點能夠更新t的距離,則刪除重復的路徑,總能獲得更短的距離。
循環終止條件:隊列中沒有新產生更新的節點時,則已求得最小距離

bfs-box(p,b,t):for each x as player:for each y as box:inQueue[(x,y)] = falsed[(x,y)] = INFQ = {(p,b)}# d[x]是當前已經計算的寬度樹高度下的最小距離d[(p,b)] = 0ans = INFwhile Q is not empty:u = Q.poll()inQueue[u] = falseif u[1] == t:if ans > d[u]:ans = d[u]continue# 這里比較關鍵,每當一個狀態的距離被松弛以后,需要將其重新加入隊列中,后續會更新該節點的最小值for (v,s) in move(u):if d[u] + s > d[v]:d[v] = d[u] + sif not inQueue[v]:Q.add(v)inQueue[v]=truemove(u):返回4個鄰接點

注意,該算法同樣適用于求最短路徑。
上面算法的運行時間實際上會比較長,因為一個點會被反復加入到隊列中更新直到它的路徑不能夠再松弛位置。
因此我們也可以將算法描述為:找到每個可到達的點的所有松弛路徑中的最小值
首先是確保每個點都會被訪問到,通過move函數保證
然后確保所有松弛路徑,也是通過move函數的四個相鄰狀態保證的。

while 隊列中存在產生新的松弛距離的點:更新其相鄰的點的松弛距離,將產生更新的點加入到隊列中

解4:減小BFS的狀態數,分別對人和箱子進行BFS
人的位置需要BFS到箱子的4個相鄰位置,只需要判斷是否能夠進行即可,每個箱子的位置有4個狀態,表示人所處的位置。箱子則需要在4個狀態下,搜索到目標的4個狀態之一。

divided-bfs(p,b,t):Q = {}for dir in dirs:p = (b,dir,0)if not search-player(player,p.player):continueQ.add(p)visited[p.state]=truewhile Q is not empty:u = Q.poll()if u.box == t:return u.dfor v in move-box(u):visited[v] = trueQ.add(v)v.d = u.d + 1# u包含箱子位置和人的相對位置 move-box(u):list = positions of ufor p in list:p.available = not visited[p.state] && search-player(u.player,p.previousPlayer)return list# 搜索人從p到達s的路徑 search-player(p,s):if p==s:return trueif s is invalid:return falsefor each state:visited[state]=falseQ = {p}visited[p]=truewhile Q is not empty:u = Q.poll()if u==s:return truefor v in move-point(u):if not visited[v]:visited[v]=trueQ.add(v)return false

復雜度分析:O(V^2)

4.魔方

題目:給一個2x2的迷你魔方每個面編號,賦予一個數字;最多轉動這個魔方n次,求魔方的每一面4個數字的積的總和的最大值。
:使用回溯法,主要是狀態轉化有點麻煩,純手寫就行。

5.俄羅斯方塊

6.拼圖游戲

7.五子棋

題目:判斷五子棋哪一方贏棋

:五子棋需要8個方向是否滿足贏棋規則,因此需要統計一個棋子的8個方向的累積和。8個方向的累積和各有具有迭代性質,因此,我們遍歷某個方向時,需要保證下一個點的累積數據一定能夠根據之前的累積數據直接計算出來。

我們使用的遍歷方法: 左上角 --> 右下角:解決左,左上,上的統計, 右上角-->左下角:解決右,右上的統計, 以及左下角->右上角和右下角-->左上角的統計。

此外,我們可以初始化一個nn8的數組,一次遍歷解決,只需要在一次遍歷中執行8個更新操作即可。
或者我們執行n*n次循環,單獨維護四個方向的遍歷即可。

# points和dirs存儲 dirs=[[(-1,0,0), // 左 第三個下標表示的是sum中的位置(-1,-1,1), // 左上]...(1,1), // 右下 ]points = [(0,0),(0,n-1),(n-1,0), (n-1,n-1)] sum = new int[n][n][8] for i = 1 to n*n:for j = 1 to 4:x,y = points[j]for dir in dirs[j]:prevX,prevY = x+dir[0],y+dir[1]prevSum = 0if prevX,prevY is valid and the same color:prevSum = sum[prevX][prevY][dir[2]]sum[x][y][dir[2]]=prevSum+1if prevSum+1 >= 5:print("win")points[j] = next(points,j)

其他還需要注意,黑棋和白棋至多相差一個子,且累積的數目至多是5。

5.數獨

題目:數獨是一個9x9的宮格里,每一行,每一列,每個3x3的格子(一共9個)中1-9只能出現一次
鏈接:https://leetcode-cn.com/problems/sudoku-solver/


解法1:使用回溯法依次判斷每一個空格。
優化方案:減少無效的重試,當一個宮格使用之后,該行的所有格子,該列的所有格子,該3x3宮格的格子可使用候選就減少了。一共有9行,9列,9個3x3格子,每一個格子都有其所屬的行,列和3x3格子下標。

使用一個3x9的數組,記錄行、列、3x3格子的占用情況。
解法2:使用Dancing Links
Knuth提出了解決精確覆蓋問題的方案,所謂精確覆蓋是指給定多個限制條件和選擇范圍,選擇出所有的解。
首先選擇一列,然后選擇選擇相交的行,移除這個行的所有已選擇的列,同時移除關聯這些列的行。

https://github.com/rafalio/dancing-links-java/blob/master/www-cs-faculty.stanford.edu/~uno/papers/dancing.ps.gz

精確覆蓋問題,考慮下面的矩陣,找出其中能夠覆蓋所有列都有1的所有行。比如1,4,5行。

AlgorithmX:對于一個矩陣A
1.如果A是空的,則已經產生了解
2.如果A非空,則選擇一列c
3.選擇這一列中的一行r, 是的A[r,c]=1
4.對行r的每一列j, 如果A[r,j]=1
從矩陣A中刪除列j
對j的每一行i, 如果A[i,j]=1
從矩陣A中刪除行i
5.在減小的矩陣上繼續應用該算法

矩陣的每一個點使用4個節點連接

使用數組來存儲矩陣,令U[x],D[x],L[x],R[x]分別表示上鄰接點,下鄰接點,左鄰接點,右鄰接點。C[x]表示該點的列頭,S[x]表示該列的大小,N[x]表示該列的名稱。

列頭的L,R鏈接了所有仍需解決的列(約束條件)。
一個特殊的點:h,表示頭部,僅L[h], R[h]有效。

偽代碼

search(k):if h.R == h:print solutionreturnc = choose_column()cover(c)r=c.Dwhile r!=c:O[k] = rj=r.Rwhile j!=r:cover(j)j = j.Rsearch(k+1)r=O[k]c=r.Cj=r.Lwhile j!=r:uncover(j)j = j.Luncover(c)

choose_column的實現,可以選擇根節點h的右節點,也可遍歷選擇右節點中最少的一個:

choose_column():j=h.Rs=while j!=h:if s>j.S:c=js=j.Sreturn c

cover和uncover操作分別從矩陣中移除或恢復列c以及相關的行,以及這些行對應的列

cover(c):c.R.L = c.Lc.L.R = c.Ri=c.Dwhile i!=c:j=i.Rwhile j!=i:j.D.U = j.Uj.U.D = j.Dj.C.S--j = j.Ri=i.D # 注意uncover其實就是cover的逆轉操作 uncover(c):i=c.Uwhile i!=c:j=i.Lwhile j!=i:j.C.S++j.D.U = jj.U.D = jj=j.Li=i.Uc.R.L=cc.L.R=c

我們使用一個boolean函數w構造矩陣:

init(n,m,w):h = new Node()leftHead = NILleft = NIL# 按行遍歷,需要記錄每一列的最近upup = new Node[m]for i=0 to n:for j=0 to m:if w(i,j)==0:continuet = new Node()# 更新最近的左節點if leftHead == NIL:left = leftHead = tleft.R = tt.L = leftleft = t# 更新最近的頭部if up[j] == NIL:up[j].C = up[j] = tup[j].D = tt.U = up[j]t.C = up[j].Cup[j]=tif leftHead != NIL:if i==0:h.R = leftHeadh.L = leftleftHead.L = leftleft.R = leftHeadleftHead = left = NIL

數獨問題轉化為精確覆蓋問題:列是約束條件,數獨游戲共有4類約束條件
1.每個位置只能放一個數字
2.每一行必須放置滿所有的數字
3.每一列必須放置滿所有的數字
4.每個3*3的區域必須放置滿所有的數字
這些約束使用列約束表示就是:
1.第i個位置放置一個數,共有9*9個位置,所以有9*9列
2.第i行出現j 9行,9個數字,共9*9列
3.第i列出現j,9列,9個數字,共9*9列
4.第i個區域出現j,9個區域,9個數字,共9*9列
所以總共有9*9*4=324列

行是解的狀態,即第i行j列放置k,共9行,9列,9個數字,所以總共9*9*9行,即729行。

初始化:從給定的數獨狀態中,已經有一些固定的行狀態為true,所以分別將4個約束條件對應的列置1。然后構造矩陣。
尋找解:將矩陣中所有已經確定的解移除,剩余一個尚未解決的矩陣;目標是移除所有的列,最終獲得解,因此,對每一行,嘗試將其加入解;然后遞歸解決這個問題即可。

建議:使用dancing links解決過于復雜,但是該思想值得借鑒。

5.貪吃蛇

題目:拼圖游戲

字符串

1.KMP查找算法

復雜度O(N+P), N是字符串長度,P是模式長度

2.模式匹配

3.簡單正則表達式

12.字符串交換的最大連續字符數

問題:字符串S由小寫字母構成,長度為n。定義一種操作,每次都可以挑選字符串中任意的兩個相鄰字母進行交換。詢問在至多交換m次之后,字符串中最多有多少個連續的位置上的字母相同?

:我們知道最終的最大連續的字符肯定是26個小寫字母中的一個,我們分別求出26個字母的最大值進行比較即可。
現在來看求字符v的最大值,顯然字符串中v可以分為n個區間,每個區間包含長度信息len以及區間之間的間隔d。
由于需要連續字符,所以顯然將所有字符往某個位置移動是最好的策略,因為如果不這樣,一部分字符往a處移動,另一部分往b出移動,兩個策略產生的值一定小于全部往a處移動或全部往b處移動。因為將一部分a移動到b總能產生比b更大的連續數,將一部分b往a移動也總能產生比a更大的連續數。

可以采用遍歷的方法求得最大值。

5.判斷兩個字符串是否具有相同的組成

由于字符串的元素的個數有限,可以統計每個字符出現的次數,然后比較即可。

6.簡單模式字符串匹配問題

問題:模式是包含.和*的字符串,待匹配的字符串不包含* ,判斷模式與字符串是否匹配

f(i,j)表示模式[i,m-1]與字符串[j,n-1]匹配的問題。

如果模式P[i]不是.*,c*的模式,則問題轉化為 f(i+1,j+1)
如果模式P[i]是.*, 則問題轉化為 f(i+1,j) 和 f(i+1,j+2), 如果模式是c*,則問題轉化為 f(i+1,j+2), 以及當A[i]=c時, f(i+1,j)

二位坐標

1.包圍點

題目:給定二維平面上的點,尋找所有的包圍點。包圍點即該點的右上方沒有其他的點(右上方是指 (x,y)均大于該點的范圍)。

解法:我們考察包圍點的性質,發現最左側的包圍點,一定是y最大的;第二個包圍點,一定是除了第一個包圍點之外最大的。
因此,首先將點集按y降序,x升序排序;使用xmin記錄當前最左側的點,然后依次驗證每一個點:如果x<xmin,則該點不是解;否則,該點是解,加入解集,同時更新xmin=x

2.矩形覆蓋問題

問題1:391. Perfect Rectangle

給定n個矩形,判斷這些矩形是否精確地構成一個完整的矩形,其中不存在重疊和遺漏

:對矩形按左下標排序,當左下標x相同時,按y排序;如果存在兩個相同的左下標,則不能構成完美矩形。將排序結果加入到優先級隊列Q中,當每次取出Q頭部x坐標相同的,判斷它們的左邊是否覆蓋完整矩形的左邊。然后,將其中寬度最小的矩形用于切割其他矩形,將剩下的矩形重新加入隊列中。

給定n個矩形,判斷重疊最多的點(邊界不算重疊)。

矩形的基本操作

判斷給定的兩個坐標是否是一個有效的矩形

只需要第一個下標嚴格地位于第二個下標的左下角

isValidRectange(x0,y0,x1,y1):return x0<x1 && y0<y1

求重疊部分

求出交叉部分,判斷是否有效

findCover(x00,y00,x01,y01,x10,y10,x11,y11):xm0 = max(x00,x10)ym0 = max(y00,y10)xm1 = min(x01,x11)ym1 = min(y01,y11)if isValid(xm0,ym0,xm1,ym1):return (x0=xm0,y0=ym0,x1=xm1,y1=ym1)return NIL

切割

首先,我們考慮被矩形內部的一個部分切割的情況

// 注意cover必須完全在矩形內 // cover最多將矩形分成4個部分 // ................. // . . // . ..... . // . . . . // . ..... . // . . // ................. cutByCover(x0,y0,x1,y1,cover):subRectangles = {(x0,y0,cover.x0,y1),(cover.x0,y0,x1,cover.y1),(cover.x0,cover.y1,x1,y1),(cover.x1,y0,x1,cover.y1) }for e in subRectangles:if not isValidRectange(e):remove e from subRectanglesreturn subRectangles

然后兩個矩形的切割,就是兩個矩形與cover部分的切割:

cutRectange(x00,y00,x01,y01,x10,y10,x11,y11):cover = findCover(x00,y00,x01,y01,x10,y10,x11,y11)if cover == NIL:return [(x00,y00,x01,y01),(x10,y10,x11,y11)]return cutByCover(x00,y00,x01,y01,cover) + cutByCover(y01,x10,y10,x11,y11,cover)

排列組合

1.把數組排成最小的數

問題:一個正整數數組A,將其所有數字拼接成一個數,求拼接的最小值

:這個拼成的數據最終的位數是固定的,我們考慮最高位,一定是最高位最小的數字填充。但是最高位最小的數字長度不一,會限制第二個高位。我們可以證明一定是選擇順序最低的那位。對于長度相同的數字顯然是,對于長度較短的數字,則優先選擇長度較短的那個。因為次高位可填的數字必然頭部必然還是最低的。

因此最終的方案就是講數組轉換成字符串數組,比較函數是:nm和mn誰更小。

2.第k個排列組合

問題:每個單詞都包含n個’a’和m個’z’, 并且所有單詞按照字典序排列,找出第k個單詞是什么。

鏈接:https://www.nowcoder.com/practice/12b1b8ef17e1441f86f322b250bff4c0?tpId=98&tqId=32838&tPage=1&rp=1&ru=/ta/2019test&qru=/ta/2019test/question-ranking

:一般性的問題,給定一個字符集合,求這些字符排列的第k字典序的值。

假定我們已經確定了某個排列A,如果兩個字典序的前綴相同,則順序取決于后綴的順序。因此,設A的下一個排列是A’, 設它們的最長公共前綴是T, 后綴是F和F’,則顯然F’是F的下一個排列。

因此,A’等于A的所有后綴中,能夠產生最相鄰排列中的最短的那個。只要一個排列還沒有達到逆序,則它就能產生下一個排列。

next(A) = for s = A的長度為2到n的后綴,如果next(s)存在,則返回 (A-s) + next(s)

由于next(s)同樣會遍歷后綴,因此最終的結果就是尋找最短后綴中,尚未達到逆序的那個排列,因為該后綴最短,所以處理前面的兩個數字是遞增的,其他數字都是遞減的。
也就是具有下面的結構:

. i-1(逆序的點). ..i .i+1.. .i+2. ..i+3

交換A[i]與[i+1,n-1]中大于A[i]的第一個元素,從而第i位遞增,但是此時[i+1,n-1]并不處于最小的狀態,因為最小的狀態是單調遞增。所以,將數組后綴元素逆序,就構成了單調遞增狀態。

next(A):n=A.lengthi=n-2while i>=0 && A[i] >= A[i+1]:--iif i<0:return NIL# 交換 A[i] 與 A[i]右側的第一個大于A[i]的元素# 使用二分查找,查找右側第一個比,因為右側是排序的# a b c d# c b a d# d b c a# c和d進行比較,當c!=d時,選擇較小的一個,也就是最后一個大于a的元素# 當c==d時,顯然交換c,也就是相同元素最左側的那個j = binary-search-with-min-bigger(A,A[i],i+1,n-1)exchange A[i],A[j]reverse(A,i+1,n-1)return A # 返回處的元素必須滿足: A[r]>m, 且A[r-1] > A[r] binary-search-with-min-bigger(A,m,i,j):while i<j:# r必須是中間偏右側的位置,因為后面還需要進行逆序操作r=(i+j+1)/2if A[r] <= m:j = r - 1else:i = rreturn i reverse(A,i,j):while i<j:int tmp=A[i]A[i++]=A[j]A[j--]tmp

算法總結:每一步操作O(N),如果需要查找第K個,需要O(KN)

解2:由于題目中只有’a’,'z’兩種數字,所以考慮f(i,j)為包含i個’a’和j個’z’的的問題,可以劃分為子問題f(i-1,j)和f(i,j-1)。

dp = new int[n+1][m+1]f(i,j,m):d = g(i-1,j)if d==m:return 'a' + 'z'*j + 'a'*(i-1)elif d>m:return 'a' + f(i-1,j,m)h = g(i,j-1)if h + d == m:return 'z'*j + 'a'*ielif h + d > m:return 'z' + f(i,j-1,d-m)return NILg(i,j):if i<=0 && j<=0:return 0elif i==0||j==0:return 1if dp[i][j]!=-1:return dp[i][j]return dp[i][j] = g(i-1,j) + g(i,j-1)

解3:由于問題中只包含兩個字母,因此排列的問題轉化成組合的問題,其本質就是求C(n+m, m)

3.所有排列

如果不要求排列有序,實際上我們可以使用下面的遞歸方程獲得所有排列:

f(S,i) = “a” + f( S - {a}, i+1)
其中a是屬于S中的任意元素,且子遞歸中,a的數量應當減一;i表示排列長度

數字

1.最大的三個數乘積

題目:給定一個無序數組,包含正數、負數和0,要求從中找出3個數的乘積,使得乘積最大,要求時間復雜度:O(n),空間復雜度:O(1)

鏈接:https://www.nowcoder.com/practice/5f29c72b1ae14d92b9c3fa03a037ac5f?tpId=90&tqId=30776&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking

解的分析:

// 沒有正數時,選擇最大的三個數相乘 // 含有1個正數時,選擇正數和最小的兩個數相乘 // 含有2個正數時,如果只有3個元素, 則三個數相乘即可;否則,選擇較大的正數和 非正數中絕對值最大的其他兩個數相乘 // 含有3個以上正數時, 選擇下列元素中最大的值: // 三個最大的正數 // 最大的正數 和 非正數中絕對值最大的兩個數

其中,絕對值大的兩個非負數,其實就是最小的兩個數(當數組中非負數含有至少2個時)。
因此,最終的結果只與5個數有關:最大的3個數和最小的2個數。

2.排列的數字是否被3整除

題目:數列按f(i) = f(i-1) + string(i) 的方式展開: 1, 12, 123,…12345678910,1234567891011…,給定i,j,試計算i到j之間能夠被3整除的數的個數。

鏈接:https://www.nowcoder.com/practice/51dcb4eef6004f6f8f44d927463ad5e8?tpId=98&tqId=32825&tPage=1&rp=1&ru=/ta/2019test&qru=/ta/2019test/question-ranking

:我們知道能夠被3整除的數,其各位數字之和一定能夠被3整除。因此,問題的關鍵就是計算第i到j位,每一個數字的數字和與3的模。

實際上,i的各位之和與3的模,本身就等于i與3的模。這是因為數字每遞增1,它的各位之和與3的模也保持遞增1。

而且,這個模是存在周期的:
1 2 0 1 2 0 … 1 2 0

令k=i%3,k可取0,1,2

所以實際上 f(i) = i%3

也就是說,數列中的第i項與3的模,等于i與3的模k,加上小于k大于0的所有數字和的模。因此,如果i%3==0, 則f(i)=0, 如果i%3==1,則f(i)=1;如果i%3==2,則f(i)=0.
因此,僅當i%3==1時不能被整除,其他情況下都可以。

對于區間i,j,考察i和j所處的位置,j-i+1表示[i,j]的數字總數,令k=j-i+1 / 3, r=j-i+1%3, s=r這一部分的能被3整除的數目
則k表示其中不能被3整除的數目,r則表示余下的數字,如果j%i==1,則s=r-1;如果j%i==0,則s=r;如果j%i==2,則s=r-1

public static int count(int m,int n){int k = (n-m+1)/3;int h = (n-m+1)%3;int s = 0 ;if(h>0){int w = n%3;if(w==0){s += h;}else if(w==2){s += 1;}}return 2*k + s; }

關鍵點:模3的數字和等于該數模3.

3.乘方冪的第K項

題目 鏈接

已知一個正整數n,(3 <= n <= 15),將所有n的乘方冪以及所有n的乘方冪(有限個且互不相等)之和組成一個遞增序列。例如,當n為4時,該序列為:1, 4, 5, 16, 17, 20, 21……
(40, 41, 40+41, 42, 40+42, 41+42, 40+41+42……)
請求出該序列的第K項


實際上,我們構建出一個三角形:

0
1 1,0
2 2,0 2,1 2,1,0
3 3,0 3,1 3,1,0 3,2 … 3,2,1,0

我們發現,每一層都是前面所有層的相加。

實際上,這個排列中,第K個數等于K所在的高度的起始值以及差值。
設h(K)為其高度, d(K)為差值,則
f(K) = nh(K) + f(d(K)), 顯然最終f(K) = ni0 + ni1 + … + nik
而ik就是K的二進制表示中所有為1的位,因為h(K)就是K的最高位,h(d(K))就是次高位,依次下去。

從另外一個角度看,我們可以考慮特殊的2進制數: A0*n0+A1*n1 + A2*n2 + … + Ai*ni,其中Ai==0或1,顯然A0 A1 A2 … Ai組合成的二進制數就是該二進制數空間的順序,因此只需要將K表示為二進制數,然后賦予A0…Ai,即可求得f(K).

cur = n^0 c = 0 while K>0:if K & 0b1 == 1:c += curcur *= nK >>>= 1 return c

4.丑數

題目:1201 Ugly Number III, 264 Ugly Number II, 313 Super Ugly Number

一個丑數是僅能被a,b或c整除的數,給定a,b,c, 和n,求第n個丑數

:丑數能夠用表示為 ax * by * cz
考慮我們已知前k個丑數,如何生成第k+1個丑數?

我們使用隊列Q表示所有已生成的k個丑數,則第k+1個丑數必然是a,b或者c的乘積,如果Sk+1 = Sk * a,則Sk = Sk+1/a必然屬于已經生成的丑數,同理對Sk+1/b,Sk+1/c也是如此。
所以,第k+1個丑數必然是隊列Q中的某一個丑數與a,b或c的乘積,且是大于已知最大丑數Sk的最小值。

所以我們只需要將隊列的值從小到大依次與a,b,c相乘并取滿足大于丑數Sk的最小值即可。

for e in Q:s = min(e*a,e*b,e*c)if s>Sk:Q.add(s)break

優化:我們注意到求第k和第k+1個丑數時,都需要對a*e進行重復計算并求最小值,而這個最小值實際上就是上一個元素,因此,我們可以記錄上次計算最小值的最后元素,后面循環時繼續使用

i=1,j=1,k=1 s = min(Q[i]*a, Q[j]*b,Q[k]*c) if s == Q[i]*a:++i elif s== Q[j]*a:++j else:++k

上面的代碼使用i,j,k來保存a,b,c最后一次相乘能夠產生大于序列Q中所有元素下標。
循環不變式:Q[j],Q[j],Q[k]是與a,b,c相乘產生的結果的不在隊列中的最小值,這是它們能夠產生隊列下一個元素的關鍵條件。

生成序列問題

所謂生成序列問題,是指在給定一個現有隊列Q,一個候選元素集合C,選擇Q與C中的兩個元素生成下一個Q中的元素。每次生成元素時,C與Q會生成一個候選集合S,選擇S中的最優元素加入Q即得下一個元素。

實際上,丑數問題的候選元素集合C={a,b,c}與隊列Q的生成值可以刻畫為3個有序數組:

[0]: a*e1,a*e2,a*e3,... [1]: b*e1,b*e2,b*e3,... [2]: c*e1,c*e2,c*e3,...

我們知道S中的最小元素就是[0],[1],[2]中的最小元素,并且要求該元素未被加入到集合中。

所以,實際上整個生成過程就是對S進行3路歸并排序的過程。

上面討論的生成序列問題都是在候選元素C與隊列中Q中的每一個元素結合都是公平的,也就是說S是一個矩陣

下面的問題時,給定一個正整數n, 表示n^0, n^1, n^0 + n^1, ...的序列,我們分析發現S在每次生成時并不都是公平的,因為對每個元素ei,要排除ei中已經出現過的元素,因此不能使用上面的序列生成算法。

Q = e1 e2 e3 C = { n^0 n^1 n^2 ... n^k} S = [0]: e1+n^0 e1+n^1 ... [1]: e2+n^0, e2+n^1 ... ...

超級丑數

題目:313 Super Ugly Number

給定一個排序的包含k個素數的數組,尋找第n個僅由該數組元素組成的數

此問題區別于一般的丑數問題,因為一般丑數問題都保證給定丑數小于不會出現 a*b > c的情況;此外,1是第一個丑數。所以,每次遇到下一個元素和最后一個元素相同時,總是跳過這個元素。

也可以在遇到相同元素時,跳過這些元素。
主要,3個素數時,同樣需要手動跳過這些元素。

class Solution {static class Node {int e;Node next;public Node(int e){this.e = e;}}public int nthSuperUglyNumber(int n, int[] primes) {Node[] its = new Node[primes.length];Node tail = new Node(1); for(int i=0;i<primes.length;++i){its[i] = tail;}for(int i=1;i < n;++i){int minPrimeIdx = -1;int lastProd = -1;for(int j=0;j<primes.length;++j){int prod = primes[j]*its[j].e;if(lastProd==-1||prod<lastProd){lastProd = prod;minPrimeIdx = j;}}// 如果相同,則跳過這個數字,這里主要是因為冷啟動階段的問題if(tail.e==lastProd){its[minPrimeIdx] = its[minPrimeIdx].next;--i;continue;}tail.next = new Node(lastProd);tail = tail.next;its[minPrimeIdx] = its[minPrimeIdx].next;}return tail.e;} }

上面的node節點也可以換成數組,使用鏈表的主要原因是即時回收內存,避免一次性分配過多元素。

5.平方組合

題目 279. Perfect Squares

給定一個數n,n可以被表示為多個數的平方,比如10 = 32 + 12, 求n的平方表示中最少數目

本題是普通的動態規劃: f(n) = min { f(n-X) + 1 } , X是小于n的所有平方數。
為什么不考慮 f(n) = min { f(n-i) + f(i) } 這種普通的分割呢?因為最終所有的分割都會表達成某個數的平方。

設計模式

1.抽象工廠模式

網絡

1.DNS協議

數據庫

  • 中間件

  • 總結

    以上是生活随笔為你收集整理的【OfferX】常见题目的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    13日本xxxxxⅹxxx20| 精品久久久久免费极品大片 | 婷婷视频在线观看 | 欧美国产日韩在线观看 | 91精品久久香蕉国产线看观看 | 亚洲国产精品传媒在线观看 | 日本女人b| 在线观看亚洲精品视频 | 日韩激情视频 | 婷婷网站天天婷婷网站 | 免费a现在观看 | 在线观看亚洲成人 | 欧美一区二区在线刺激视频 | 天天干天天天 | 免费成人黄色 | 91国内产香蕉| 97在线精品视频 | 91理论电影 | 超碰人人99 | 啪一啪在线 | 91视视频在线直接观看在线看网页在线看 | 在线观看免费高清视频大全追剧 | 伊在线视频 | 精品国产伦一区二区三区免费 | 黄色成人影院 | 91色吧| 综合精品在线 | 欧美aa级 | 久久视频在线观看中文字幕 | 国产精品美女在线 | 国产中年夫妇高潮精品视频 | 五月婷婷播播 | 亚洲年轻女教师毛茸茸 | 四虎www.| 精品国产一区二区三区久久久蜜臀 | 国产伦理久久 | 中文字幕精| mm1313亚洲精品国产 | 久久视频免费在线 | 丰满少妇在线 | 国产精品成人自产拍在线观看 | 精品久久网站 | 亚洲人天堂 | 亚洲精品久久久久www | 国产精品成人av久久 | 一区二区三区在线视频观看58 | 天天做天天爱夜夜爽 | 日本在线视频一区二区三区 | 性色av一区二区三区在线观看 | 伊人国产在线播放 | aaa毛片视频 | 欧美国产精品久久久久久免费 | 天天色婷婷 | 91看片淫黄大片一级在线观看 | 欧日韩在线 | 天堂av在线网站 | 99视频偷窥在线精品国自产拍 | 国产精品扒开做爽爽的视频 | 国产精品成人自拍 | 欧美日韩国产成人 | 91九色porny在线 | 国产精品6| 日韩中文字幕视频在线观看 | 日韩视频免费看 | 国产黄 | 国产乱视频 | 91在线播放视频 | 国产成人精品久 | 日韩免费网站 | av在线亚洲天堂 | 成人精品久久 | 国产午夜视频在线观看 | 日韩免费成人av | 97人人添人澡人人爽超碰动图 | 美女久久久久久久 | 国产美女精品久久久 | 91av视频导航 | 国产日韩精品一区二区三区在线 | av天天干 | 丁香午夜婷婷 | 亚洲电影一区二区 | 国产1级毛片 | 伊人久久av| 中文字幕在线视频一区二区 | 97国产精品亚洲精品 | 亚洲美女在线一区 | 国产精品久久久av | 激情中文在线 | 国产精品视频在线观看 | 国产色视频网站 | 久热爱 | 日本久久久久久久久 | 亚洲精品麻豆视频 | 亚洲va在线va天堂va偷拍 | 亚洲一区二区观看 | 久久成人国产精品免费软件 | 在线成人短视频 | 激情久久久 | 久久久久免费网 | 黄a在线观看 | 成人免费共享视频 | 日韩激情影院 | 在线观看视频你懂的 | av电影免费观看 | 精品国产伦一区二区三区免费 | 成人一区二区三区中文字幕 | 久久久久久久久久免费视频 | 毛片网免费 | 欧美日韩精品在线免费观看 | 午夜av网站 | 91成品人影院 | 91电影福利| 亚洲精品免费在线视频 | 日韩影视在线 | 亚洲精品国产精品乱码在线观看 | 久草色在线观看 | 99久久久久久久久 | 欧美日韩中文字幕视频 | 国产精品久久久久久久久久不蜜月 | 精品久久国产精品 | 天天操夜夜干 | 天天草av | www.色五月.com | 91视频免费观看 | 亚洲日韩欧美一区二区在线 | 亚洲国产精品传媒在线观看 | caobi视频 | 国产精品一区二区白浆 | 狠狠的操| 色香com.| 色99导航| 天天射天天干天天操 | 色a综合 | 色综合天天色 | 欧美极品在线播放 | 丁香六月婷婷综合 | 久草a视频| 国产日韩欧美在线一区 | 又黄又爽又色无遮挡免费 | 久久久免费av | 亚洲免费国产视频 | 国产午夜三级一区二区三桃花影视 | 国产视频久久久久 | 欧美日韩在线观看不卡 | 深夜视频久久 | 亚洲春色成人 | 色香com.| 亚洲一级免费电影 | 欧美视频网址 | 中文字幕日本在线观看 | 这里有精品在线视频 | 欧美日韩在线观看视频 | 激情五月在线观看 | 丁香色婷| 美女视频a美女大全免费下载蜜臀 | 在线黄色观看 | 成人影视片 | 激情五月***国产精品 | www.久久久久 | 日韩一区二区三区在线看 | 粉嫩av一区二区三区免费 | 日本成人黄色片 | 欧美二区视频 | 精品国内自产拍在线观看视频 | 成人免费观看网址 | 在线看污网站 | 美女视频久久 | 97福利在线| 97av在线视频免费播放 | 国产成人99av超碰超爽 | 亚洲视频在线观看 | 在线观看理论 | 天天综合网久久综合网 | 成人a v视频 | 91热这里只有精品 | 亚洲成av人影院 | 国产亚州av| 久久新视频 | 7799av| 成人午夜影院 | 美女久久久久久久久久久 | 91禁在线观看 | 久久精品视频观看 | 国产午夜一区二区 | 免费福利片| 香蕉视频免费看 | www日韩在线观看 | 日日夜夜精品 | 最近中文字幕国语免费高清6 | 韩国在线视频一区 | 国产尤物在线观看 | 黄色网址中文字幕 | 中文字幕人成一区 | 精品色999| 久草在线国产 | av观看久久久 | 日韩成人黄色 | 国产国语在线 | 91网站免费观看 | 久久99免费视频 | 99免费| 精品美女久久 | 99中文视频在线 | 98久久 | 亚洲国产欧美在线人成大黄瓜 | 日韩精品不卡在线 | 中文字幕在线视频网站 | 91麻豆福利 | 天天色天天综合网 | 六月丁香色婷婷 | 五月婷婷综合激情网 | 中文字幕91在线 | av在线免费在线观看 | 国产一二区在线观看 | 日韩电影一区二区三区 | 色综合久久综合中文综合网 | 日韩| 国产无遮挡又黄又爽馒头漫画 | 免费久久视频 | 中文字幕无吗 | 亚洲国产69 | 免费网站色| 亚洲精品国产品国语在线 | 婷婷丁香六月 | 91精品国产91热久久久做人人 | 国产精品一区二区电影 | 欧美日韩在线看 | 日批网站免费观看 | 国产精品久久久久久久久久99 | 免费看色网站 | 免费黄色网址网站 | 成年人黄色免费视频 | 日韩激情精品 | 天天天干夜夜夜操 | 日本久久中文 | 菠萝菠萝在线精品视频 | 天天干人人 | 日韩一区二区三区高清在线观看 | 国产成人a亚洲精品v | 欧美一级日韩三级 | 日韩中文字幕免费电影 | 亚洲激精日韩激精欧美精品 | 深爱五月网 | 国内精品久久久久久中文字幕 | 99视频这里有精品 | 麻豆 91 在线 | 天天干.com| 男女视频国产 | 人人爽久久久噜噜噜电影 | 久久久久久99精品 | 中文字幕在 | 一区中文字幕在线观看 | 国产黄a三级三级三级三级三级 | 欧美精品一区二区蜜臀亚洲 | 欧美精品做受xxx性少妇 | 国产福利精品一区二区 | 五月天网站在线 | 国产69精品久久99不卡的观看体验 | 97成人免费视频 | 99精品国产成人一区二区 | 91av久久| 欧美极度另类 | 欧美精品乱码99久久影院 | 99热国内精品 | 国产一线二线三线在线观看 | 国产不卡免费 | 欧美日本不卡高清 | 中文字幕精品三级久久久 | 中文字幕在线看视频国产中文版 | 国产精品videoxxxx | 日韩在线观看第一页 | 亚洲禁18久人片 | 超碰在线97免费 | 日韩欧美在线国产 | 99爱视频| 国产色拍拍拍拍在线精品 | 亚洲天堂网在线视频 | 国产亚洲视频中文字幕视频 | 最近乱久中文字幕 | 成人av亚洲 | 天天躁日日躁狠狠躁 | 日日操天天射 | 午夜av一区二区三区 | 久久综合九色综合久99 | 国产精品免费久久久 | avhd高清在线谜片 | 九九九热 | 午夜久操 | 日韩视频在线观看视频 | 玖玖999 | 亚洲精品玖玖玖av在线看 | 97视频网址 | 午夜国产在线观看 | 成人av高清在线 | 91中文字幕在线播放 | 中文字幕在线观看视频网站 | 在线观看涩涩 | 日本一区二区三区免费看 | 99这里精品 | 久久久久久免费毛片精品 | 国产视频91在线 | 国产 日韩 在线 亚洲 字幕 中文 | 一区二区电影在线观看 | 日本巨乳在线 | 久久精品区 | 久久九九影视 | 欧美日韩一区二区三区视频 | 精品国产一区二区三区久久久蜜臀 | 蜜臀aⅴ国产精品久久久国产 | 伊人久在线 | 午夜av在线 | 国产成人黄色片 | 中文在线字幕观看电影 | 91一区啪爱嗯打偷拍欧美 | 天天插天天干天天操 | 日韩午夜在线观看 | 婷婷亚洲激情 | 国产精品一区二区精品视频免费看 | 欧美精品九九99久久 | 日本久久久亚洲精品 | 中文字幕丝袜制服 | 国产精品porn | 成人黄色短片 | 亚洲精品综合欧美二区变态 | 麻豆影音先锋 | 国产精彩视频一区二区 | 久久久久免费精品国产 | 久久久久一区二区三区四区 | 国产精品岛国久久久久久久久红粉 | 国产少妇在线观看 | 涩涩伊人 | 四虎最新入口 | 久久999精品 | 国产精品第二十页 | 婷婷色社区 | 久久理论片 | 天天色天天综合 | 日韩丝袜在线观看 | 成人一区二区三区在线 | 中文字幕不卡在线88 | 欧美精品在线观看免费 | 狠狠做深爱婷婷综合一区 | 麻豆一区二区三区视频 | 国产精品色 | 免费a级毛片在线看 | www成人精品| 欧美va电影 | 国语精品久久 | 国产原创在线观看 | a v在线视频 | 精品免费在线视频 | 丁香六月国产 | 黄污视频大全 | 婷婷色在线播放 | 国内精品视频免费 | 中文字幕一区2区3区 | 六月丁香伊人 | 国产精品九九九 | 国产淫片免费看 | 天天操天天爱天天干 | 欧美另类交在线观看 | 中文字幕亚洲五码 | 国产精品99免视看9 国产精品毛片一区视频 | 成人黄色在线观看视频 | 成人免费观看a | 亚洲视频资源在线 | 久草久草在线观看 | 91视频网址入口 | 中文字幕制服丝袜av久久 | 99热网站| 免费一级片久久 | 国产成人l区 | 欧美亚洲成人xxx | 人人讲| 中文字幕你懂的 | 亚洲免费高清视频 | 99在线精品免费视频九九视 | 免费看wwwwwwwwwww的视频 久久久久久99精品 91中文字幕视频 | 81精品国产乱码久久久久久 | 91精品色 | av免费看在线 | 亚洲另类视频在线 | 97夜夜澡人人双人人人喊 | 久久久影院一区二区三区 | 在线观看免费观看在线91 | 激情综合婷婷 | 黄色视屏在线免费观看 | 国产最新精品视频 | 三级黄色在线观看 | 日韩最新在线视频 | 成人香蕉视频 | 精品成人免费 | 在线观看 国产 | 久久97超碰| 人人澡人人爽欧一区 | 成人国产网址 | 免费在线观看av的网站 | 久久成人免费视频 | 五月激情综合婷婷 | 天天摸天天舔天天操 | 伊人中文在线 | 亚洲午夜精品久久久久久久久 | 成人97视频一区二区 | 日韩视| 婷婷亚洲最大 | 国产午夜三级一二三区 | 成人在线观看免费 | 色综合久久久久综合体 | 在线观看视频在线观看 | 精品色综合 | 欧美日韩免费观看一区=区三区 | 国精产品999国精产品视频 | 成人午夜毛片 | 国产不卡av在线播放 | 国产精品久久久久久久久久久杏吧 | av网站在线免费观看 | 中文字幕免费国产精品 | 欧美极品少妇xxxx | 99免费在线视频 | 国产成人久久av免费高清密臂 | 欧美激情综合色综合啪啪五月 | 激情婷婷亚洲 | 欧美日韩国产一区二区三区 | 久久9999久久免费精品国产 | 国产91亚洲| 国产乱视频 | 狠狠色丁香久久婷婷综合丁香 | 免费看污污视频的网站 | 亚洲激情在线 | a级免费观看 | 久久电影中文字幕视频 | 激情在线网址 | 一级黄色在线视频 | 久久综合一本 | 国内久久精品 | 日韩av看片 | 夜夜躁日日躁狠狠躁 | 日韩av网站在线播放 | 在线观看视频国产一区 | 欧美精品一级视频 | 久久精品毛片 | 免费一级毛毛片 | 91视频在线播放视频 | 久久精品伊人 | 天天摸天天干天天操天天射 | 日韩视频一 | 成人18视频 | 欧美日韩一区二区在线观看 | 久久成人一区二区 | 91久久在线观看 | 色综合欧洲 | 亚洲久草在线 | av免费观看网址 | 天堂av免费在线 | 免费在线观看成人 | 最近2019好看的中文字幕免费 | 99一级片 | 综合伊人av| 在线涩涩 | 五月综合网 | 国产精品麻豆免费版 | a极黄色片| 在线不卡a | 免费看的黄色网 | 免费看黄在线网站 | 玖玖视频免费在线 | 国产色黄网站 | 91在线视频播放 | 亚洲视频免费在线观看 | 黄色成人av | 五月天婷婷在线视频 | 日日干天天爽 | 色婷婷a | 奇米777777| 亚洲精品在线电影 | 欧女人精69xxxxxx | 日韩一区二区三区高清免费看看 | www在线免费观看 | 五月天色丁香 | 四虎永久免费 | 中文字幕在线高清 | 国产精品久久久久久电影 | 青青河边草免费 | 欧美久久久| 日韩精品高清视频 | 欧美精品乱码久久久久久按摩 | 最近中文字幕在线 | av电影在线免费观看 | 国产91国语对白在线 | 成 人 黄 色 免费播放 | 日韩在线视频网址 | 精品久久久久免费极品大片 | 青青河边草免费观看完整版高清 | 久久免费视频网站 | 国产高清在线精品 | 99热官网| 亚洲最新av网站 | 午夜国产一区二区三区四区 | 免费看污污视频的网站 | 午夜天使 | 精品国产自在精品国产精野外直播 | 天堂av在线网站 | 婷婷丁香自拍 | 日韩av电影网站在线观看 | 精品国产亚洲日本 | 在线免费观看黄色av | 四虎影视4hu4虎成人 | 久久伦理网 | 黄色一级大片在线免费看产 | 午夜精品久久久久久中宇69 | 91精品视频导航 | 久久精品播放 | 国产精品亚洲片在线播放 | 色综合久久久久久久 | 色婷五月 | 久草99| 黄色av一区二区三区 | 玖玖视频 | 久久免费福利 | 在线电影中文字幕 | 99热手机在线 | 一区二区电影网 | 天天搞天天干 | 天天草天天插 | 一区二区三区久久精品 | 欧美一区日韩精品 | 日韩av在线资源 | 久久都是精品 | 最新中文在线视频 | 美女视频一区二区 | 黄色免费视频在线观看 | 午夜美女福利 | 国产精品成人自产拍在线观看 | 国产精品美女久久久久久久 | 国产一级免费av | 在线视频免费观看 | 亚洲精品视频在线看 | 超碰在线亚洲 | 不卡的av在线播放 | 最近日本韩国中文字幕 | 亚洲精品毛片一级91精品 | 国产高清免费视频 | 九九国产视频 | 免费在线观看日韩视频 | 91精品在线观看视频 | 亚洲精品国偷自产在线99热 | 超碰夜夜| 欧美福利在线播放 | 成人黄色电影免费观看 | 欧洲色吧 | 国产美女无遮挡永久免费 | 中文字幕在线国产精品 | 久久久久五月天 | 91视频电影 | 国产一级大片在线观看 | 亚洲精品国偷拍自产在线观看 | 婷婷丁香av | 精品中文字幕在线观看 | 免费成人在线观看 | 视频在线在亚洲 | 成人天堂网 | 国产精品久久 | 久久久久国产精品www | 九九九毛片 | 色综合久久久久 | 国产欧美久久久精品影院 | 久久这里只有精品首页 | 91av视频| 国产理论免费 | 欧美整片sss | 激情欧美一区二区免费视频 | 日韩欧美视频在线观看免费 | 国产精品白丝jk白祙 | 亚洲网久久 | 黄色在线小网站 | 日韩激情一二三区 | 成年人黄色免费网站 | 成人永久在线 | 亚洲精品自在在线观看 | 日韩免费视频线观看 | 香蕉手机在线 | 国产福利av| 久久精品中文 | 91精品国产欧美一区二区 | av亚洲产国偷v产偷v自拍小说 | 天天色天天操天天爽 | 国产乱对白刺激视频不卡 | 在线免费观看的av | 亚洲视频免费在线观看 | 久久黄色片子 | 亚洲精品欧美精品 | 91探花国产综合在线精品 | 亚洲一区天堂 | 欧美美女一级片 | 成人a v视频 | 久久精品视频在线免费观看 | 黄色免费av| 午夜精品一二三区 | 亚洲高清资源 | 91最新视频 | 超级碰视频 | 美女一区网站 | 亚洲精品成人在线 | 在线播放日韩av | 精品一区二区在线免费观看 | 日韩激情视频在线观看 | 在线视频黄 | 一区在线播放 | 婷婷色资源 | 91大神精品视频在线观看 | 日韩系列 | 国产精品mm| 国产美女视频 | 黄色免费高清视频 | 天天综合久久综合 | 一区二区成人国产精品 | 日韩三级一区 | 精品99在线观看 | 国产乱码精品一区二区三区介绍 | 亚洲精品日韩在线观看 | 午夜性生活 | 一区二区三区免费在线 | 色网av | 99热手机在线 | 日一日干一干 | 最近2019好看的中文字幕免费 | 久久艹久久 | 超碰国产在线 | 最近免费中文字幕 | 免费韩国av | 亚洲欧美视频在线观看 | 久久99精品久久久久蜜臀 | 精品久久美女 | 亚洲成a人片77777kkkk1在线观看 | 狠狠精品 | 中文字幕国产精品 | 国产免费中文字幕 | 天天夜夜亚洲 | 2018好看的中文在线观看 | 成人性生交视频 | 国产精品入口久久 | 曰本免费av | 色婷婷www| 国产精品一区二区在线观看 | 亚洲专区欧美专区 | 欧美久久久久久久 | 就要干b | 国产精品系列在线播放 | 亚洲精品久久久蜜桃直播 | 久草在线视频免费资源观看 | 天天天天干| 伊人色综合久久天天网 | 日韩午夜精品福利 | 亚洲伊人网在线观看 | 激情网在线视频 | 人人爱人人做人人爽 | av不卡中文字幕 | 久久国产精品99久久久久 | 97理论片| 亚洲二级片 | 国产高清免费视频 | 成人av在线看 | 亚洲婷久久 | 日韩美女免费线视频 | 国产香蕉视频在线观看 | 中文字幕乱视频 | 亚洲高清网站 | 日韩免费电影一区二区 | 天天干天天操天天入 | 久久看片网站 | 91在线亚洲 | 久久精品99 | 亚洲永久精品一区 | 91精品视频免费看 | 婷婷色社区| 一区二区三区国产欧美 | 91在线看 | 日韩欧美99| 在线免费三级 | 久久免费毛片 | 国产视频精品久久 | 激情综合狠狠 | 91成年人在线观看 | 最近日本中文字幕a | 99精品国产99久久久久久福利 | 蜜桃视频成人在线观看 | av 一区 二区 久久 | 日韩在线字幕 | 在线免费色视频 | 亚洲欧美日韩国产精品一区午夜 | 国产午夜三级 | 欧美天天综合网 | 九色在线 | 国产二级视频 | 亚洲精品中文在线资源 | 欧美激情精品久久久久久免费 | 精品久久1 | 亚洲综合色站 | 午夜黄网 | 免费看的国产视频网站 | 日本精品视频网站 | 2019中文字幕网站 | 亚洲精品www久久久久久 | 伊人五月婷 | 成人午夜久久 | 五月婷婷在线视频 | 国产专区视频在线 | 欧美在线视频免费 | 中文字幕 国产精品 | 婷婷综合亚洲 | 男女激情片在线观看 | 婷婷综合在线 | 欧美成a人片在线观看久 | 麻豆国产电影 | 日本丰满少妇免费一区 | 午夜av影院 | 天天操天天操天天操天天操 | 免费看wwwwwwwwwww的视频 久久久久久99精品 91中文字幕视频 | 九九在线高清精品视频 | 日韩1级片 | av中文字幕在线播放 | 久久视频在线看 | 欧美精品免费在线 | 成年人在线免费看视频 | 一级黄色a视频 | 国产精品九九视频 | 亚洲精品国产精品久久99 | 午夜久久久久久久久 | 97在线免费观看 | 久久超碰在线 | 狠狠综合久久 | 99热在线国产| 中文网丁香综合网 | 片黄色毛片黄色毛片 | 久久久99精品免费观看乱色 | av播放在线 | 国产资源精品 | 国产精品永久 | 免费福利在线 | 日韩欧美在线观看一区二区三区 | 91免费高清视频 | 精品国产一区二区三区日日嗨 | 天天操狠狠干 | 91av网站在线观看 | 国产综合婷婷 | 黄色在线免费观看网站 | 四虎国产精品成人免费4hu | 久久精品79国产精品 | 天天操夜夜操夜夜操 | av资源网在线播放 | 日日操狠狠干 | www五月天com | 97视频资源| 久久午夜电影院 | 国产成人精品一区一区一区 | 天天干天天草天天爽 | 色欲综合视频天天天 | 91福利社区在线观看 | 娇妻呻吟一区二区三区 | 中文字幕美女免费在线 | 超碰在线个人 | 一级大片在线观看 | 国产精品日韩欧美一区二区 | 天天操夜夜曰 | 草久在线播放 | 九色视频网站 | 91九色精品 | 日韩三级中文字幕 | 久久激情婷婷 | 激情 婷婷| 成年人电影免费在线观看 | 亚州天堂| 又黄又刺激视频 | 日产中文字幕 | 三级av在线免费观看 | 波多野结衣理论片 | 欧美视频日韩 | 麻豆91精品视频 | 在线观看成人福利 | 成人av资源网站 | av网站在线观看播放 | 精品一区91 | 免费视频一二三区 | 欧美国产日韩一区 | 久久婷婷网 | 在线精品播放 | 五月天伊人网 | 久久美女高清视频 | 欧美aaa视频 | 久操久| 狠狠色丁香久久综合网 | 国产精品99久久久久久久久久久久 | 日日干精品 | 成人四虎 | 亚洲一区 影院 | 在线亚洲欧美日韩 | 欧美日韩性| 91久久国产综合精品女同国语 | 欧美日韩中文字幕视频 | 中文超碰字幕 | 国内精品久久久久 | av在线官网 | 天天操夜夜摸 | 久久综合99| 在线亚洲播放 | 麻豆视频免费在线播放 | 久久午夜国产精品 | 啪啪动态视频 | 五月综合久久 | 黄色软件在线看 | 免费在线观看午夜视频 | 亚洲视频 在线观看 | 69久久99精品久久久久婷婷 | 欧美91视频 | 国产精品自在线 | 精品国产一区二区三区久久 | 国产天天爽| 999成人精品 | 丁香婷婷综合激情五月色 | 日韩免费小视频 | 精品主播网红福利资源观看 | 一区二区三区久久精品 | 99r在线 | 国产成人性色生活片 | 国产99视频在线观看 | 中文字幕三区 | 国产精品一区一区三区 | 最近中文字幕免费大全 | 中文字幕在线观看第一区 | 日本爽妇网 | 国产69久久| 天天操网址 | 日韩成人免费在线 | 欧美精品久久天天躁 | 日韩狠狠操 | 亚洲精品美女久久17c | 亚洲国产三级在线观看 | 成人动漫一区二区 | 91在线免费公开视频 | 97国产一区 | 久久午夜精品视频 | 91av资源在线| av天天色| 麻豆视频免费入口 | 婷婷激情网站 | 91免费在线视频 | 日韩精品在线免费观看 | 国产精品久久久久国产精品日日 | 久久国产精品99精国产 | 久久婷婷一区 | 久久国产精品免费看 | 久久国产视频网 | 久久精品国产亚洲a | 中文字幕在线字幕中文 | 香蕉视频一级 | 久久久久久久网 | 日韩久久一区二区 | 婷婷在线色 | 开心婷婷色 | 国内视频在线 | 麻花天美星空视频 | 天天色天天艹 | 国产一级黄色电影 | 97天堂| 91精品国产九九九久久久亚洲 | 婷久久 | 少妇bbbb搡bbbb搡bbbb | 国产麻豆精品久久 | 亚洲一区二区三区miaa149 | 丁香婷婷久久久综合精品国产 | 色综合久久中文字幕综合网 | jizz999| 国产精品一级视频 | 在线免费av观看 | 久久综合精品国产一区二区三区 | 日本精品视频一区 | 黄色的视频网站 | 国产美女精品久久久 | 国产中文 | 欧美日韩另类在线观看 | 亚洲精品国产精品国产 | 日韩性色 | 国产精品色 | 在线国产精品视频 | 五月天色中色 | 日韩免费观看高清 | 天天射天天操天天色 | 香蕉久草| 四虎在线观看 | 色中色亚洲 | 亚洲成人黄 | 欧美视频在线二区 | 91精品无人成人www | 日本久久精 | 99精品乱码国产在线观看 | 在线直播av | 国产视频1区2区 | 片网址| 人人插人人射 | 久久精品国产精品亚洲 | 亚洲精品av中文字幕在线在线 | 人人爽久久久噜噜噜电影 | 激情婷婷久久 | 97偷拍视频| 日本中文字幕在线播放 | 美女久久久久久 | 久草视频在线免费 | 黄色片网站大全 | 国产又粗又猛又黄 | a电影在线观看 | 日韩欧美在线国产 | 91视频国产高清 | 日本黄色免费在线 | a v在线视频 | 97超碰香蕉 | 麻豆传媒视频在线播放 | 成人黄色在线视频 | 亚洲丁香日韩 | 97视频在线观看成人 | 婷婷丁香色| 黄色网在线免费观看 | 日韩欧美亚州 | 波多野结衣一区二区 | 国产精品成人久久久久久久 | 手机av在线网站 | 欧美aaa视频 | 在线免费av播放 | 午夜精品久久久久久99热明星 | 日p视频 | 91精品国产一区二区三区 | 丁香在线视频 | 香蕉视频91 | 毛片基地黄久久久久久天堂 | 亚洲午夜久久久久久久久电影网 | 在线视频 国产 日韩 | 国产精品久久久久久久久久了 | 免费看av片网站 | 丁香六月在线观看 | 黄色一级大片在线免费看产 | 欧美日韩一区二区久久 | 99精品视频中文字幕 | av7777777| 三级毛片视频 | 午夜久久电影网 | 激情网婷婷 | 麻豆网站免费观看 | 在线观看中文字幕一区 | 蜜臀av性久久久久蜜臀aⅴ流畅 | 九九免费观看视频 | 九九99视频 | 天天爽天天碰狠狠添 | 99色在线观看视频 | 日韩av高潮 | 精品uu | 国产美女免费看 | 欧美一区二区三区四区夜夜大片 | 欧美精品网站 | 天天色天天射综合网 | 午夜av激情 | 国产高清亚洲 | 色视频在线免费 | 成年人黄色av | 日p视频在线观看 | 国产精品一区二区在线看 | 久久久久久久久久久久电影 | 国产在线精品区 | 久久免费视频5 | 久久在视频 | 久久九九久久精品 | 日韩资源在线观看 | 一区二区激情视频 | 国产成人av免费在线观看 | 国产欧美三级 | 91成人天堂久久成人 | a级国产乱理伦片在线观看 亚洲3级 | 日韩视频1区 | 久久久久国产精品视频 | 天天天插| 国产精品福利小视频 | 国产欧美日韩精品一区二区免费 | 狠狠色丁香婷婷综合基地 | 欧美高清成人 | 亚洲综合色播 | 9999亚洲| 国产精品一二三 | 天天操天天干天天操天天干 | 又黄又爽又色无遮挡免费 | 国内精品久久久久国产 | 在线视频你懂得 | 国产在线视频资源 | 黄色小网站在线观看 | 色综合天天干 | 九色激情网 | 中文在线8新资源库 | 色婷婷免费视频 | 国产成人精品亚洲日本在线观看 | 免费av在线网站 | 亚洲美女精品 |