Arithmetic Slices II - Subsequence LT446
Posted taste-it-own-it-love-it
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Arithmetic Slices II - Subsequence LT446相关的知识,希望对你有一定的参考价值。
A sequence of numbers is called arithmetic if it consists of at least three elements and if the difference between any two consecutive elements is the same.
For example, these are arithmetic sequences:
1, 3, 5, 7, 9 7, 7, 7, 7 3, -1, -5, -9
The following sequence is not arithmetic.
1, 1, 2, 5, 7
A zero-indexed array A consisting of N numbers is given. A subsequence slice of that array is any sequence of integers (P0, P1, ..., Pk) such that 0 ≤ P0 < P1 < ... < Pk < N.
A subsequence slice (P0, P1, ..., Pk) of array A is called arithmetic if the sequence A[P0], A[P1], ..., A[Pk-1], A[Pk] is arithmetic. In particular, this means that k ≥ 2.
The function should return the number of arithmetic subsequence slices in the array A.
The input contains N integers. Every integer is in the range of -231 and 231-1 and 0 ≤ N ≤ 1000. The output is guaranteed to be less than 231-1.
Example:
Input: [2, 4, 6, 8, 10] Output: 7 Explanation: All arithmetic subsequence slices are: [2,4,6] [4,6,8] [6,8,10] [2,4,6,8] [4,6,8,10] [2,4,6,8,10] [2,6,10]
Idea 1. BruteForce. 穷举所有符合条件的序列,序列其实是数组的subsets, 用DepthFirst Search穷举subsets.
Time complexity: O(2^n) For each element in the array, it can be put in or outside of the subsequence, two choices for each element.
Space complexity: stack depth O(n)
1 class Solution { 2 private boolean isArithmeticSequence(int[] A, int currDep) { 3 if(currDep < 3) { 4 return false; 5 } 6 7 long diff = (long)A[1] - A[0]; 8 for(int i = 2; i < currDep; ++i) { 9 if(diff != (long)A[i] - A[i-1]) { 10 return false; 11 } 12 } 13 return true; 14 } 15 16 private void helper(int[] A, int depth, int[] path, int pathPos, int[] count) { 17 if(depth == A.length) { 18 if(isArithmeticSequence(path, pathPos)) { 19 ++count[0]; 20 } 21 return; 22 } 23 24 helper(A, depth+1, path, pathPos, count); 25 path[pathPos] = A[depth]; 26 helper(A, depth+1, path, pathPos+1, count); 27 } 28 29 public int numberOfArithmeticSlices(int[] A) { 30 int[] path = new int[A.length]; 31 int[] count = new int[1]; 32 helper(A, 0, path, 0, count); 33 return count[0]; 34 } 35 }
Note: 1. reset the change on the current depth before backtracking to the previous depth. List implementation is more obvious, as array just keep the int index pathPos unchanged.
2. Overflow, change int to long to filter out invalid cases, as there is no valid arithmetic subsequence slice that can have difference out of the Integer value range.
1 class Solution { 2 private boolean isArithmeticSequence(List<Integer> curr) { 3 if(curr.size() < 3) { 4 return false; 5 } 6 7 long diff = (long)curr.get(1) - curr.get(0); 8 for(int i = 2; i < curr.size(); ++i) { 9 if(diff != (long)curr.get(i) - curr.get(i-1)) { 10 return false; 11 } 12 } 13 return true; 14 } 15 16 private void helper(int[] A, int depth, List<Integer> curr, int[] count) { 17 if(depth == A.length) { 18 if(isArithmeticSequence(curr)) { 19 ++count[0]; 20 } 21 return; 22 } 23 24 helper(A, depth+1, curr, count); // not put A[depth] in the subsequence 25 26 curr.add(A[depth]); 27 helper(A, depth+1, curr, count); // put A[depth] in the subsequence 28 29 curr.remove(curr.size()-1); // reset before backtracking 30 } 31 32 public int numberOfArithmeticSlices(int[] A) { 33 int[] count = new int[1]; 34 helper(A, 0, new ArrayList<>(), count); 35 return count[0]; 36 } 37 }
python:
1 class Solution: 2 def isArithmetic(self, curr: List[int]) -> bool: 3 if(len(curr) < 3): 4 return False; 5 6 diff = float(curr[1]) - curr[0] 7 for i in range(2, len(curr)): 8 if diff != float(curr[i]) - curr[i-1]: 9 return False; 10 11 return True 12 13 def helper(self, A: List[int], depth: int, curr: List[int], count: List[int]) -> None : 14 if depth == len(A): 15 if self.isArithmetic(curr): 16 count[0] += 1 17 18 return 19 20 self.helper(A, depth+1, curr, count) 21 curr.append(A[depth]) 22 self.helper(A, depth+1, curr, count) 23 curr.pop() 24 25 def numberOfArithmeticSlices(self, A: List[int]) -> int: 26 count = [0] 27 self.helper(A, 0, [], count) 28 return count[0]
Idea 2: Dynamic programming, similar to Arithmetic Slices LT413, how to extend from solution to nums[0...i] to nums[0..i, i+1]? LT413的sequence要求是连续的,只需要检查能否延续前一位为结尾的序列,一维的关系:dp(i) = dp(i-1) + 1; 而这一题可以跳过前面的数,延续前面任何以nums[j]结尾的满足条件的序列(0 <j <i, diff(nums[k, j]) = nums[i] - nums[j]),需要加入序列的差d来表达关系,用dp(i, d)表示以nums[i]结尾,序列差为d的序列个数,
dp(i, d) = sum(dp(j, d) + 1)
序列要求是三位数的长度,如果以3位数为base case这个并不好计算,如果放松一下条件2位数算作wealy arithmetic sequence, 上面的公式依然成立,2位数的base case也好计算,
dp(i, nums[i]-nums[j]) = 1 for any pair j, i, 0 <= j < i
我们来走一下例子:[1, 1, 2, 3, 4, 5]
i = 0, dp(0, d) = 0
i = 1, j = 0, diff = 1 - 1 = 0, dp(1, 0) = 1, sequence: [1, 1]
i = 2, j = 0, diff = 2 - 1 = 1, dp(2, 1) = 1; j = 1, diff = 2 - 1 = 1, dp(2, 1) = 1 + 1 = 2 sequence: [1, 2], [1, 2]
i = 3, j = 0, diff = 2, dp(3, 2) = 1; j = 1, diff = 2, dp(3, 2) = 2; j = 2, diff = 1, dp(3, 1) = dp(2, 1) + 1 = 3, sequence: [1, 3], [1, 3], [1, 2, 3], [1, 2, 3], [2, 3]
i = 4, j = 0, diff = 3, dp(4, 3) = 1; j = 1, diff = 3, dp(4, 3) = 2; j = 2, diff = 2, dp(4, 2) = 1; j = 3, dp(4, 1) = dp(3, 1) + 1 = 4, sequence: [1, 4], [1, 4], [2, 4], [1, 2, 3, 4], [1, 2, 3, 4], [2, 3, 4], [3, 4]
i = 5, j = 0, diff = 4, dp(5, 4) = 1, sequence[1, 5]; j = 1, diff = 4, dp(5, 4) = 2, sequence [1, 5] [1, 5]; j = 2, diff = 3, dp(5, 3) = 1; j = 3, diff = 2, dp(3, 2) = 2, dp(5, 2) = dp(3, 2) + 1 = 3, sequence [1, 3, 5], [1, 3, 5], [3, 5]; j = 4, dp(5, 1) = dp(4, 1) = 5, sequence [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [2, 3, 4, 5], [3, 4, 5], [4, 5]
从例子可以看出来符合至少3位数的序列个数其实取决于前面sequence个数dp(j, d), 公式中的+1是pair (nums[j], nums[i])2位数的序列,总结公式如下:
dp(i, d) = sum(dp(j, d) + 1)
dp(i, nums[i]-nums[j]) = 1 for any pair j, i, 0 <= j < i
result(3位数的序列个数) = sum(dp(j, d))
由于 d是unbounded可正可负,一般dynamic programming使用二维数组做memory就不能用了,而用array of map, dp(i).get(d) = dp(i, d)
Time complexity: O(n2)
Space complexity: O(n2)
1 class Solution { 2 public int numberOfArithmeticSlices(int[] A) { 3 int result = 0; 4 List<Map<Integer, Integer> > dp = new ArrayList(); 5 for(int i = 0; i < A.length; ++i) { 6 dp.add(new HashMap()); 7 for(int j = 0; j < i; ++j) { 8 long delta = (long)A[i] - A[j]; 9 if(delta < Integer.MIN_VALUE || delta > Integer.MAX_VALUE) { 10 continue; 11 } 12 int diff = (int) delta; 13 int prev = dp.get(j).getOrDefault(diff, 0); 14 int curr = dp.get(i).getOrDefault(diff, 0); 15 dp.get(i).put(diff, curr + prev + 1); 16 result += prev; 17 } 18 } 19 return result; 20 } 21 }
array of map
1 class Solution { 2 public int numberOfArithmeticSlices(int[] A) { 3 int result = 0; 4 Map<Integer, Integer>[] dp = new Map[A.length]; 5 for(int i = 0; i < A.length; ++i) { 6 dp[i] = new HashMap<>(); 7 for(int j = 0; j < i; ++j) { 8 long delta = (long)A[i] - A[j]; 9 if(delta < Integer.MIN_VALUE || delta > Integer.MAX_VALUE) { 10 continue; 11 } 12 int diff = (int) delta; 13 int prev = dp[j].getOrDefault(diff, 0); 14 int curr = dp[i].getOrDefault(diff, 0); 15 dp[i].put(diff, curr + prev + 1); 16 result += prev; 17 } 18 } 19 return result; 20 } 21 }
python:
1 class Solution: 2 def numberOfArithmeticSlices(self, A: List[int]) -> int: 3 dp = [{} for _ in range(len(A))] 4 result = 0 5 for i in range(len(A)): 6 for j in range(i): 7 delta = A[i] - A[j] 8 prev = dp[j].get(delta, 0) 9 curr = dp[i].get(delta, 0) 10 dp[i][delta]= curr + prev + 1 11 result += prev 12 13 return result
Idea 3. 前面我们提到如果以3位数为base case这个并不好计算,换一个角度nums[i] - nums[j] = nums[j] - nums[k], 0 <= k < j < i, 如果有nums[k] = nums[j] * 2 - nums[i], 需要快速地找到nums[k],我们需要一个map记录nums[k] 和 index k.
dp[i][j] = sum(dp[j][k] + 1)
base case dp[i][j] = 0
result = sum(dp[i][j])
我们来走一下例子:[1, 1, 2, 3, 4, 5]
lookup(nums[k], [k]): 1-> [0, 1] , 2-> [2], 3-> [3], 4-> [4], 5-> [5]
i = 2, j = 1, nums[k] = 0, 不存在;
i = 3, j = 1, nums[k] = 2 * 1 - 3= -1,不存在; j= 2, nums[k] = 2 * 2 - 3 = 1, dp[3][2] += dp[2][0] + 1 + dp[2][1] + 1 = 2, sequence [1,2,3], [1, 2, 3]
i = 4, j = 1, nums[k] = 2 * 1 - 4 = -2, 不存在; j= 2, nums[k] = 2 * 2 - 4 = 0, 不存在; j = 3, nums[k] = 2 * 3 - 4 = 2, dp[4][3] += dp[3][2] + 1 = 3, sequence: [1,2,3, 4], [1, 2, 3, 4], [2, 3, 4]
i = 5, j = 1, nums[k] = 2 * 1 - 5 = -3, 不存在; j= 2, nums[k] = 2 * 2 - 5 = -1, 不存在; j = 3, nums[k] = 2 * 3 - 5 = 1, dp[5][3] = dp[3][1] + 1 + dp[3][0] + 1 = 2; j = 4, nums[k] = 2 * 4 - 5 = 3, dp[5][4] += dp[4][3] + 1 = 4, sequence: [1, 3, 5], [1, 3, 5], [1, 2, 3, 4, 5], [1, 2,3,4,5], [2, 3, 4, 5], [3, 4, 5]
Time complexity: O(n3) the worest case to loop the map lookup could be nearly as O(n), when have lots of duplicates like 1, 1, 1, 1, 2, 3, 4
Space complexity: O(n2)
1 class Solution { 2 public int numberOfArithmeticSlices(int[] A) { 3 int result = 0; 4 Map<Integer, List<Integer>> lookUp = new HashMap<>(); 5 int[][] dp = new int[A.length][A.length]; 6 7 for(int i = 0; i < A.length; ++i) { 8 if(lookUp.get(A[i]) == null) { 9 lookUp.put(A[i], new ArrayList<>()); 10 } 11 lookUp.get(A[i]).add(i); 12 } 13 14 for(int i = 2; i < A.length; ++i) { 15 for(int j = 1; j < i; ++j) { 16 long tempTarget = 2 * (long)A[j] - A[i]; 17 if(tempTarget < Integer.MIN_VALUE 18 || tempTarget > Integer.MAX_VALUE) { 19 continue; 20 } 21 int target = (int) tempTarget; 22 if(lookUp.containsKey(target)) { 23 for(int k: lookUp.get(target)) { 24 if(k < j) { 25 dp[i][j] += dp[j][k] + 1; 26 } 27 } 28 result += dp[i][j]; 29 } 30 } 31 } 32 return result; 33 } 34 }
python
1 class Solution: 2 def numberOfArithmeticSlices(self, A: List[int]) -> int: 3 result = 0 4 dp = [collections.defaultdict(int) for _ in range(len(A))] 5 lookup = collections.defaultdict(list) 6 7 for i, val in enumerate(A): 8 lookup[val].append(i) 9 10 for i in range(2, len(A)): 11 for j in range(1, i): 12 target = 2 * A[j] - A[i] 13 if target in lookup: 14 for k in lookup[target]: 15 if k < j: 16 dp[i][j] += dp[j][k] + 1 17 18 result += dp[i][j] 19 20 return result
以上是关于Arithmetic Slices II - Subsequence LT446的主要内容,如果未能解决你的问题,请参考以下文章
Arithmetic Slices II - Subsequence LT446
LeetCode446. Arithmetic Slices II - Subsequence