本周关键词:状态压缩DP,树状数组,归并排序,拓扑排序。 方法一:状压DP n个工作分配给k个工人,求让每个工人的最大工作时长最小。观察数据范围:k,n (1~12),考虑状态压缩。枚举出n种工作的所有分配状态。
设dp[i][j]表示i个工人分配了j状态工作的最大时长,j用来表示状态。那么Dij= min(max(dp[i][k],max(j-k))) k表示所有j的子集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public int minimumTimeRequired (int [] jobs, int k) { int n = jobs.lenth; int m = 1 <<n; int [][] dp = new int [n][m]; int [] sum = new int [m]; for (int i=1 ;i<m;i++){ int j = Integer.numberOfTrailingZeros(i),x = i^(1 <<j); sum[i] = sum[x] + jobs[j]; dp[0 ][i] = sum[i]; } for (int i=1 ;i<k;i++){ for (int j=1 ;j<m;j--){ int maxn = Integer.MAX_VALUE; for (int k=j;k!=0 ;k=(k-1 )&j) { maxn = Math.min(maxn,Math.Max(sum[k],dp[i-1 ][j-k])); } } } return dp[k-1 ][m-1 ]; }
求数组中严格递增的最长子序列长度
方法一: 暴力DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int lengthOfLIS (int [] nums) { int n = num.length; int [] dp = new int [n]; Arrays.fill(dp,1 ); for (int i=1 ;i<n;i++){ for (int j=0 ;j<i;j++){ dp[i] = Math.max(dp[i],dp[j]+1 ); } } int res = 0 ; for (int num : dp) res = Math.max(num,res); return res; } }
方法二: DP+二分 更改状态定义,dp[i]表示的是长度为i的递增子序列的最小结尾,那么根据贪心性,如果dp[i]越小,那么后续可能的递增序列越长,起码不会短。同时可以保证dp一定是递增的。如果dp[i]==5,dp[i-1]==7,那么长度为i的递增序列的最大值为5,一定存在i-1序列的最大值<5,故dp[i-1]<7;
那么,遍历nums,在dp中找到dp[i-1]<nums[k]<dp[i],那么dp[i] = nums[k],此过程可以二分法加速。
时间复杂度:O(nLogn)
空间复杂度:O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public int lengthOfLIS (int [] nums) { List<Integer> dp = new ArrayList<>(); dp.add(nums[0 ]); for (int i=1 ;i<nums.length;i++){ if (nums[i]>dp.get(dp.size()-1 )) { dp.add(nums[i]); }else { dp.set(bitSearch(dp,nums[i]),nums[i]); } } return dp.size(); } public int bitSearch (List<Integer> dp,int k) { int i = 0 ,j = dp.size()-1 ; int res = 0 ; while (i<=j) { int mid = (i+j)>>1 ; if (dp.get(mid)==k) return mid; if (dp.get(mid)>k){ res = mid; j = mid-1 ; }else { i = mid+1 ; } } return res; } }
解法一: DP 与最长递增子序列求解一致,只不过此时只需保证dp的长度>=3即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution : def increasingTriplet (self, nums: List[int]) -> bool: dp = [nums[0 ]] for num in nums: if num > dp[-1 ]: dp.append(num) if len(dp)>=3 : return True else : for i in range(len(dp)): if dp[i]>=num: dp[i] = num break return False
解法一:拓扑排序+DP 矩阵点之间的最长递增可以抽象成:值小的点对于值大的点有一条有向边。整个矩阵可以抽象成一个有向图,同时递增是单向的,所以最后图是有向无环图。所以可以通过拓扑排序获得线程序列,更具线性序列进行DP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class Solution { public int longestIncreasingPath (int [][] matrix) { int m = matrix.length, n = matrix[0 ].length; int [] dx = new int []{1 ,-1 ,0 ,0 }; int [] dy = new int []{0 ,0 ,1 ,-1 }; int [][] inDegree = new int [m][n]; Deque<Integer> queue = new LinkedList<>(); List<Integer> topo = new LinkedList<>(); for (int i=0 ;i<m;i++){ for (int j=0 ;j<n;j++){ for (int k=0 ;k<4 ;k++){ int x = i+dx[k], y = j+dy[k]; if (x>=0 && x<m && y>=0 && y<n && matrix[i][j] > matrix[x][y]) inDegree[i][j]++; } } } for (int i=0 ;i<m;i++){ for (int j=0 ;j<n;j++){ if (inDegree[i][j]==0 ) queue.offerLast(i*n+j); } } while (!queue.isEmpty()) { Integer top = queue.pollFirst(); topo.add(top); int i = top/n, j = top%n; for (int k=0 ;k<4 ;k++){ int x = i+dx[k], y = j+dy[k]; if (x>=0 && x<m && y>=0 && y<n && matrix[x][y] > matrix[i][j]) { inDegree[x][y]--; if (inDegree[x][y] == 0 ) queue.offerLast(x*n+y); } } } int res = 0 ; int [] dp = new int [n*m]; for (int i=topo.size()-1 ;i>=0 ;i--) { int point = topo.get(i); int u = point/n, v = point%n; for (int k=0 ;k<4 ;k++){ int x = u+dx[k], y = v+dy[k]; if (x>=0 && x<m && y>=0 && y<n && matrix[u][v] < matrix[x][y]) dp[point] = Math.max(dp[point],dp[x*n+y]); } dp[point]+=1 ; res = Math.max(res,dp[point]); } return res; } }
解法一:2dDP 盲目dfs本题会超时,考虑:k步走到i=0的位置可以由k-1步走到i=1位置的方案数量获得,dp[i][j] = dp[i-1][j+1]+dp[i-1][j-1]
时间复杂度:O(n*k)
空间复杂度:O(n**2)/O(N)