老规矩,跳过需要付费的题目。题目是越来越不好做,我尽量把自己的思路写下来。
371 | 51.9% | Easy | |
368 | Largest Divisible Subset |
31.9% | Medium |
367 | 36.9% | Medium | |
365 | Water and Jug Problem |
24.7% | Medium |
363 | Max Sum of Rectangle No Larger Than K |
30.6% | Hard |
357 | Count Numbers with Unique Digits |
44.2% | Medium |
355 | 23.5% | Medium | |
354 | Russian Doll Envelopes |
30.6% | Hard |
352 | Data Stream as Disjoint Intervals |
38.2% | Hard |
350 | Intersection of Two Arrays II |
42.6% | Easy |
349 | Intersection of Two Arrays |
44.5% | Easy |
347 | Top K Frequent Elements |
44.4% | Medium |
345 | Reverse Vowels of a String |
36.6% | Easy |
344 | 58.1% | Easy | |
343 | 43.8% | Medium | |
342 | 36.6% | Easy | |
341 | Flatten Nested List Iterator |
35.3% | Medium |
338 | 58.6% | Medium | |
337 | 40.2% | Medium | |
336 | 22.7% | Hard | |
335 | 22.8% | Hard | |
334 | Increasing Triplet Subsequence |
36.7% | Medium |
332 | Reconstruct Itinerary |
26.8% | Medium |
331 | Verify Preorder Serialization of a Binary Tree |
33.9% | Medium |
330 | 30.8% | Hard | |
329 | Longest Increasing Path in a Matrix |
34.0% | Hard |
328 | 40.7% | Medium | |
327 | 27.7% | Hard | |
326 | 38.6% | Easy | |
324 | 24.3% | Medium | |
322 | 25.8% | Medium | |
321 | Create Maximum Number |
22.9% | Hard |
319 | 41.5% | Medium | |
318 | Maximum Product of Word Lengths |
41.3% | Medium |
316 | Remove Duplicate Letters |
27.5% | Hard |
315 | Count of Smaller Numbers After Self |
32.5% | Hard |
313 | 36.5% | Medium | |
312 | 40.4% | Hard |
【题目】
Calculate the sum of two integers a and b , but you are not allowed to use the operator +
and -
.
Example:
Given a = 1 and b = 2, return 3.
【解答】俩数相加,不许用加减号。
不能用加减号,于是考虑用位操作。但是怎样做呢?考虑位操作的几种方式,与、或、非、异或、左移、右移,哪些可以联合起来模拟加法呢?
public class Solution { public int getSum(int a, int b) { if (a==0) return b; int x = a ^ b; int c = (a & b) << 1; return getSum(c, x); } }
【题目】 Given a set of distinct positive integers, find the largest subset such that every pair (S i , S j ) of elements in this subset satisfies: S i % S j = 0 or S j % S i = 0.
If there are multiple solutions, return any subset is fine.
Example 1:
nums: [1,2,3] Result: [1,2] (of course, [1,3] will also be ok)
Example 2:
nums: [1,2,4,8] Result: [1,2,4,8]
【解答】要找这样的一个子集合,使得所有元素都可以互为整除或者被整除的关系。
在一切开始前,大脑里面大致有这样一个方向:我需要把nums排序,然后从较小的一端开始逐个审查归类,因为每查看一个新数的时候,我应该在手边有一个不断构建着的集合,这个集合根据整除关系包含了若干个分类,而且里面的数都比当前审查的数小,所以我会挨个从集合里拿出数来去尝试整除这个被审查数,根据结果去更新这个集合。
于是我建立这样一个数据结构:一个list,其中第i个元素表示以nums[i]作为最大值且必须包含它的最大分类;而每个分类又是由一串数组成,它们互为整除或被整除关系,并且升序排列。为什么要升序排列?因为排列以后,每个分类最大的那个数,可以作为分类的代表,如果有新数要比较,和它比就可以,如果它和新数存在前面提到的整除关系,那么这个分类中任何一个数都会和新数存在整除关系。这一点是解题的关键。
public class Solution { public List<Integer> largestDivisibleSubset(int[] nums) { if (nums.length==0) return new ArrayList<>(); // lens[i] means the largest divisible subset which is in asc sort and ended with nums[i] List<List<Integer>> lens = new ArrayList<>(); Arrays.sort(nums); List<Integer> first = new ArrayList<>(1); first.add(nums[0]); lens.add(first); for (int i=1; i<nums.length; i++) { Integer maxIndex = null; for (int j=0; j<i; j++) { List<Integer> list = lens.get(j); if (maxIndex!=null && list.size() <= lens.get(maxIndex).size()) continue; int prevMax = list.get(list.size()-1); if (nums[i] % prevMax == 0) { maxIndex = j; } } List<Integer> newList; if (maxIndex==null) newList = new ArrayList<>(); else newList = new ArrayList<>(lens.get(maxIndex)); newList.add(nums[i]); lens.add(newList); } List<Integer> result = null; for (List<Integer> list : lens) { if (result==null || result.size()<list.size()) result = list; } return result; } }
【题目】 Given a positive integer num , write a function which returns True if num is a perfect square else False.
Note: Do not use any built-in library function such as sqrt
.
Example 1:
Input: 16 Returns: True
Example 2:
Input: 14 Returns: False
【解答】判断一个数是不是完全平方数。
我记得小时候老师就说,要证明一个大于1的数x是不是完全平方数,可以从x/2开始试起,慢慢减小。这个可以减小计算量。结论应该是对的,但是需要证明一下以严谨一点:
x^2 – 2*x = x^2 – 2*x + 1 – 1 = (x-1)^2 -1 >= -1, when x>=2 it’s >= 0
so x^2 >= 2*x when x>=2
好了,这就可以写代码了。使用long型,防止平方的时候溢出:
public class Solution { public boolean isPerfectSquare(int num) { if (num==0 || num==1) return true; int half = num/2; for (long i=half; i>0; i--) { // long if (i*i>num) continue; else if (i*i==num) return true; else return false; } return false; } }
【题目】 You are given two jugs with capacities x and y litres. There is an infinite amount of water supply available. You need to determine whether it is possible to measure exactly z litres using these two jugs.
If z liters of water is measurable, you must have z liters of water contained within one or both buckets by the end.
Operations allowed:
Example 1: (From the famous “Die Hard” example )
Input: x = 3, y = 5, z = 4 Output: True
Example 2:
Input: x = 2, y = 6, z = 5 Output: False
【解答】看起来很熟悉,小学里就接触这一类问题的简单版。有两个壶,分别可以装x和y升水,能提供无限水的情况下,问能不能弄出z升水来。
题目描述那么简单,通用性那么强,很容易给人一个“数学定理”的感觉。事实上,还真有一个数学定理来帮助解决这个问题,如果不知道那个定理,这个题目做起来就会非常痛苦,如果知道,那就非常简单。这个定理,就叫做 裴蜀定理 (维基百科上有证明):
对任何整数a、b和它们的最大公约数d,关于未知数x和y的裴蜀等式:
ax + by = m
有整数解时当且仅当m是d的倍数。
裴蜀等式有解时必然有无穷多个整数解。
而这个问题,就可以映射到上面这个式子中去。于是我们可以求这两个壶容量的最大公约数,看看要弄出的水升数z是不是这个最大公约数的倍数。这种题居然不是hard真是天理难容……
public class Solution { public boolean canMeasureWater(int x, int y, int z) { //limit brought by the statement that water is finallly in one or both buckets if(x + y < z) return false; //case x or y is zero if( x == z || y == z || x + y == z ) return true; //get GCD, then we can use the property of Bézout's identity return z%GCD(x, y) == 0; } public int GCD(int a, int b){ while(b != 0 ){ int temp = b; b = a%b; a = temp; } return a; } }
【题目】 Given a non-empty 2D matrix matrix and an integer k , find the max sum of a rectangle in the matrix such that its sum is no larger than k .
Example:
Given matrix = [ [1, 0, 1], [0, -2, 3] ] k = 2
The answer is 2
. Because the sum of rectangle [[0, 1], [-2, 3]]
is 2 and 2 is the max number no larger than k (k = 2).
Note:
【解答】要找一个矩形,让矩形框起来的范围内,所有数的和能够不超过k,但是又要尽量大。这道题我折腾了挺长时间也没有做对,我觉得还是比较难的。下面这个解法来自讨论区的 这个帖子 ,我认为是我看到的几个解法里面比较好的。就着代码做一个说明。为了简化理解的复杂度,不妨假设列数>行数。
通过i从0到m的循环,和j从i到0的循环,形成[j, i]这样一个区间,在这样的循环内部我们会看到二维问题被转化为一维问题来求解。
分析一下复杂度:min(m,n)^2 * max(m,n) * log(max(m,n)),还是假设列数>行数以简化描述。
因此,三者相乘,就形成了这样的复杂度。
/* first consider the situation matrix is 1D we can save every sum of 0~i(0<=i<len) and binary search previous sum to find possible result for every index, time complexity is O(NlogN). so in 2D matrix, we can sum up all values from row i to row j and create a 1D array to use 1D array solution. If col number is less than row number, we can sum up all values from col i to col j then use 1D array solution. */ public int maxSumSubmatrix(int[][] matrix, int target) { int row = matrix.length; if(row==0)return 0; int col = matrix[0].length; int m = Math.min(row,col); int n = Math.max(row,col); //indicating sum up in every row or every column boolean colIsBig = col>row; int res = Integer.MIN_VALUE; for(int i = 0;i<m;i++){ int[] array = new int[n]; // sum from row j to row i for(int j = i;j>=0;j--){ int val = 0; TreeSet<Integer> set = new TreeSet<Integer>(); set.add(0); //traverse every column/row and sum up for(int k = 0;k<n;k++){ array[k]=array[k]+(colIsBig?matrix[j][k]:matrix[k][j]); val = val + array[k]; //use TreeMap to binary search previous sum to get possible result Integer subres = set.ceiling(val-target); if(null!=subres){ res=Math.max(res,val-subres); } set.add(val); } } } return res; }
【题目】 Given a non-negative integer n, count all numbers with unique digits, x, where 0 ≤ x < 10 n .
Example:
Given n = 2, return 91. (The answer should be the total numbers in the range of 0 ≤ x < 100, excluding [11,22,33,44,55,66,77,88,99]
)
【解答】要找不包含重复数字的数。主要思路还是分类讨论。
这里有一个比较容易想到的递归关系,比如n=3的时候,如果我能够拿到n=2的结果,即0≤x<100,再加上101≤x<1000之间的部分,就可以得到结果。也就是说,是对于所有一位数和两位数的解,再加上新增加的三位数的部分。
但是怎么得到这个三位数部分的解,有两种方向可以考虑(起码我是这样想的):(1)已知的所有两位数,或者是十位为0并拼接一位数的所有可能,合并起来再在百位上放一个非0的数,这样形成的三位数里面,去掉重复数字的部分;(2)单独考虑满足条件的三位数形成的所有组合,不和两位数的关联关系挂钩。如果按照思路(1)走,也许也能得出结果,但是逻辑会非常复杂;下面是按照(2)的思路来进行的步骤:
public class Solution { public int countNumbersWithUniqueDigits(int n) { int total = 0; if (n>0) total += countNumbersWithUniqueDigits(n-1); else return 1; if (n>10) return total; int product = 1; int numberToSelect = 10; for (int i=n; i>=1 ; i--) { // the highest digit, 0 is disallowed if (i==n) { product *= 9; } else { product *= numberToSelect; } numberToSelect--; } return total + product; } }
【题目】 Design a simplified version of Twitter where users can post tweets, follow/unfollow another user and is able to see the 10 most recent tweets in the user’s news feed. Your design should support the following methods:
Example:
Twitter twitter = new Twitter(); // User 1 posts a new tweet (id = 5). twitter.postTweet(1, 5); // User 1's news feed should return a list with 1 tweet id -> [5]. twitter.getNewsFeed(1); // User 1 follows user 2. twitter.follow(1, 2); // User 2 posts a new tweet (id = 6). twitter.postTweet(2, 6); // User 1's news feed should return a list with 2 tweet ids -> [6, 5]. // Tweet id 6 should precede tweet id 5 because it is posted after tweet id 5. twitter.getNewsFeed(1); // User 1 unfollows user 2. twitter.unfollow(1, 2); // User 1's news feed should return a list with 1 tweet id -> [5], // since user 1 is no longer following user 2. twitter.getNewsFeed(1);
【解答】这一类问题还是比较有趣的,因为和实际问题(需求)更接近。仔细斟酌题目中的四个case,其中3和4看起来似乎单纯和简单一些:用map就可以解决,一个用户id映射到一串用户id,以表示follow的关系。和tweet相关的1和2中,1似乎也比较容易处理,也是一个map的映射关系。麻烦的是2,一个用户可以follow若干个不同的其它用户,要返回这堆用户中最近的10条tweet,看起来似乎没有特别好的办法。因为这10条可能集中在某一个人身上,也可能分散在不同的人当中。
不过,一点是肯定的,无论是集中还是分散,任何一个人所发的tweet中,不可能取回超过10条。因此可以从该用户关注的所有人中,每个人都取得最多10条最近的tweet,然后把结果汇总起来按时间排序,返回最近的10条即可。这个思路看起来很像MapReduce那个经典的取top xx的问题。
最后,既然要涉及单个人的“最近”10条tweet,那就需要在存储这些tweet的时候:(1)按照不同的用户来归类;(2)按照时间顺序来存放。
有了这些思路,整理一下,这道题还是比较容易解出来的。这样的问题,把具体问题抽象成可以解决的数学问题,特别是找到合适的数据结构来表示这些物件之间的关系,是解决问题的关键。同为算法问题,有些问题“算”本身的逻辑比较复杂,有些问题则是把复杂性放在模型的建立上。
class TweetItem implements Comparable<TweetItem> { int time; int tweetId; public TweetItem (int time, int tweetId) { this.time = time; this.tweetId = tweetId; } @Override public int compareTo(TweetItem that) { if (this.time < that.time) return 1; else if (this.time == that.time) return 0; else return -1; } } public class Twitter { private Map<Integer, List<TweetItem>> tweets = new HashMap<>(); private Map<Integer, Set<Integer>> follows = new HashMap<>(); private int time = 0; private static int RECENT_TWEET_COUNT = 10; /** Initialize your data structure here. */ public Twitter() { } /** Compose a new tweet. */ public void postTweet(int userId, int tweetId) { if (!tweets.containsKey(userId)) tweets.put(userId, new ArrayList<>()); tweets.get(userId).add(new TweetItem(time++, tweetId)); } /** Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. */ public List<Integer> getNewsFeed(int userId) { Set<Integer> set; if (!follows.containsKey(userId)) set = new HashSet<>(); else set = follows.get(userId); // himself/herself set.add(userId); List<TweetItem> newTweets = new ArrayList<>(); for (int fId : set) { if (!tweets.containsKey(fId)) continue; List<TweetItem> subList; if (tweets.get(fId).size() > RECENT_TWEET_COUNT){ // latest 10 records subList = tweets.get(fId).subList(tweets.get(fId).size()-RECENT_TWEET_COUNT, tweets.get(fId).size()); } else { subList = tweets.get(fId).subList(0, tweets.get(fId).size()); } newTweets.addAll(subList); } Collections.sort(newTweets); List<TweetItem> subTweets; if (newTweets.size() > RECENT_TWEET_COUNT) { subTweets = newTweets.subList(0, RECENT_TWEET_COUNT); } else { subTweets = newTweets; } List<Integer> retList = new ArrayList<>(); for (TweetItem item : subTweets) { retList.add(item.tweetId); } return retList; } /** Follower follows a followee. If the operation is invalid, it should be a no-op. */ public void follow(int followerId, int followeeId) { if (!follows.containsKey(followerId)) follows.put(followerId, new HashSet<>()); follows.get(followerId).add(followeeId); } /** Follower unfollows a followee. If the operation is invalid, it should be a no-op. */ public void unfollow(int followerId, int followeeId) { if (!follows.containsKey(followerId)) return; follows.get(followerId).remove(followeeId); } }
【题目】 You have a number of envelopes with widths and heights given as a pair of integers (w, h)
. One envelope can fit into another if and only if both the width and height of one envelope is greater than the width and height of the other envelope.
What is the maximum number of envelopes can you Russian doll? (put one inside other)
Example:
Given envelopes = [[5,4],[6,4],[6,7],[2,3]]
, the maximum number of envelopes you can Russian doll is 3
([2,3] => [5,4] => [6,7]).
【解答】像俄罗斯套娃一样的套信封问题,怎样套到最多的信封。如果是一维的问题,这道题是非常简单的,从最小的信封开始,每次都尽可能去挑最小的信封套。但是现在是二维的问题(宽和高),也是题目相对来说难度增加的地方。如果把单独一维抽出来讨论,是肯定无法做到最优解的。下面是我的思路,相对简单,但是可以优化。
也就是说,整个过程里面,宽这一维做到了减少暴力解法带来的过多计算问题,因为第二层比较时只需要从i-1遍历到0;但是高这一维度却没有。
public class Solution { public int maxEnvelopes(int[][] envelopes) { if (envelopes == null) throw new IllegalArgumentException(); Arrays.sort(envelopes, new Comparator<int[]>() { public int compare(int[] left, int[] right) { if (left[0] != right[0]) { return left[0] - right[0]; } else { return left[1] - right[1]; } } }); int max = 0; int[] numberArray = new int[envelopes.length]; for (int i = 0; i < envelopes.length; i++) { numberArray[i] = 1; for (int j = i - 1; j >= 0; j--) { if (envelopes[i][0] > envelopes[j][0] && envelopes[i][1] > envelopes[j][1]) { numberArray[i] = Math.max(numberArray[i], numberArray[j] + 1); } } max = Math.max(max, numberArray[i]); } return max; } }
【题目】 Given a data stream input of non-negative integers a 1 , a 2 , …, a n , …, summarize the numbers seen so far as a list of disjoint intervals.
For example, suppose the integers from the data stream are 1, 3, 7, 2, 6, …, then the summary will be:
[1, 1] [1, 1], [3, 3] [1, 1], [3, 3], [7, 7] [1, 3], [7, 7] [1, 3], [6, 7]
Follow up:
What if there are lots of merges and the number of disjoint intervals are small compared to the data stream’s size?
【解答】首先要理解题意,随着数据流不断地流入,要把不断添加进来的数据进行分段,凡是连着的整数就算一段,每次调用getIntervals要打印出现在所有的数据段。
下面的方法是我开始思考的方法,使用TreeMap,也就是红黑树,使用它有一个好处,在于key是排序了的,这样可以用log n的复杂度找到邻近的数据段。这个map的key是数值,value是这个点的类型,分为三种:数据段开头、数据段结尾,以及数据点(即即为开头又为结尾)。然后分类讨论,用这种方法大致的方向是对的,但是下面分类讨论的case非常多,导致代码非常复杂。最后有一个巨长的测试case过不去,也不是很好调试了。为了记录思路,我把这个不好的代码例子先放在下面。
public class SummaryRanges { private static final Integer RANGE_START = 0; private static final Integer RANGE_END = 1; private static final Integer RANGE_START_AND_END = 2; private TreeMap<Integer, Integer> tm = new TreeMap<>(); /** Initialize your data structure here. */ public SummaryRanges() { } public void addNum(int val) { Map.Entry<Integer, Integer> ceiling = tm.ceilingEntry(val); Map.Entry<Integer, Integer> floor = tm.floorEntry(val); if (ceiling == null && floor == null) { // case 1: the tree map is empty tm.put(val, RANGE_START_AND_END); } else if (ceiling == null) { // case 2: put the value at the end of the ranges if (floor.getKey() == val) { ; } else if (floor.getKey() == val-1) { tm.put(val, RANGE_END); if (floor.getValue() == RANGE_END) tm.remove(floor.getKey()); else // floor.getValue() == RANGE_START_AND_END tm.put(floor.getKey(), RANGE_START); } else { tm.put(val, RANGE_START_AND_END); } } else if (floor == null) { // case 3: put the value at the beginning of the ranges if (ceiling.getKey() == val) { ; } else if (ceiling.getKey() == val+1) { tm.put(val, RANGE_START); if (ceiling.getValue() == RANGE_START) tm.remove(ceiling.getKey()); else // ceiling.getValue() == RANGE_START_AND_END tm.put(ceiling.getKey(), RANGE_END); } else { tm.put(val, RANGE_START_AND_END); } } else if (floor.getKey() == val || ceiling.getKey() == val) { // case 4: the value is already existed as the boundary of a range ; } else if (floor.getKey() == val-1) { // case 5: the value extends a lower range if (floor.getValue() == RANGE_START){ ; } else { if (floor.getValue() == RANGE_END) { tm.remove(floor.getKey()); } else { // floor.getValue() == RANGE_START_AND_END tm.put(val-1, RANGE_START); } if (ceiling.getKey() == val+1) { if (ceiling.getValue() == RANGE_START) { tm.remove(ceiling.getKey()); } else { // ceiling.getValue() == RANGE_START_AND_END tm.put(val+1, RANGE_END); } } else { tm.put(val, RANGE_END); } } } else if (ceiling.getKey() == val+1) { // case 6: the value extends a higher range if (ceiling.getValue() == RANGE_END) { ; } else { if (ceiling.getValue() == RANGE_START) { tm.remove(ceiling.getKey()); } else { // ceiling.getValue == RANGE_START_AND_END tm.put(val+1, RANGE_END); } if (floor.getKey() == val-1) { if (floor.getValue() == RANGE_END) { tm.remove(floor.getKey()); } else { // floor.getValue == RANGE_START_AND_END tm.put(val-1, RANGE_START); } } else { tm.put(val, RANGE_START); } } } else { // case 7: single point tm.put(val, RANGE_START_AND_END); } } public List<Interval> getIntervals() { List<Interval> list = new ArrayList<>(); Interval inter = null; for (Map.Entry<Integer, Integer> entry : tm.entrySet()) { if (entry.getValue() == RANGE_START_AND_END) { list.add(new Interval(entry.getKey(), entry.getKey())); } else if (entry.getValue() == RANGE_START) { inter = new Interval(); inter.start = entry.getKey(); } else { inter.end = entry.getKey(); list.add(inter); } } return list; } }
上面啰嗦的方法没再研究下去,而下面则祭出“Top Solutions”的第一个,思路就明显简洁清晰很多。也是用TreeMap,key是数值,表示的是这个数据段的开头,但是value直接放Interval对象了,这样在TreeMap中一下就可以少最多一半的数据量,因为key只需要存放开头数,不需要存放结尾数。同时,更重要的是,case也简洁很多,只需要讨论这样四种case:
public class SummaryRanges { TreeMap<Integer, Interval> tree; public SummaryRanges() { tree = new TreeMap<>(); } public void addNum(int val) { if(tree.containsKey(val)) return; Integer l = tree.lowerKey(val); Integer h = tree.higherKey(val); if(l != null && h != null && tree.get(l).end + 1 == val && h == val + 1) { tree.get(l).end = tree.get(h).end; tree.remove(h); } else if(l != null && tree.get(l).end + 1 >= val) { tree.get(l).end = Math.max(tree.get(l).end, val); } else if(h != null && h == val + 1) { tree.put(val, new Interval(val, tree.get(h).end)); tree.remove(h); } else { tree.put(val, new Interval(val, val)); } } public List<Interval> getIntervals() { return new ArrayList<>(tree.values()); } }
【题目】 Given two arrays, write a function to compute their intersection.
Example:
Given nums1 = [1, 2, 2, 1]
, nums2 = [2, 2]
, return [2, 2]
.
Note:
Follow up:
【解答】和Intersection of Two Arrays就一点区别,就是不需要去重。那就用HashMap来替代HashSet就好了,key是数字,value是该数字出现的次数。
public class Solution { public int[] intersect(int[] nums1, int[] nums2) { Map<Integer, Integer> map = new HashMap<>(); for (int num : nums1) { if (!map.containsKey(num)) map.put(num, 0); map.put(num, map.get(num)+1); } List<Integer> list = new ArrayList<>(); for (int num : nums2) { Integer val = map.get(num); if (val!=null && val!=0) { list.add(num); map.put(num, val-1); } } int[] ret = new int[list.size()]; for (int i=0; i<list.size(); i++) { ret[i] = list.get(i); } return ret; } }
【题目】 Given two arrays, write a function to compute their intersection.
Example:
Given nums1 = [1, 2, 2, 1]
, nums2 = [2, 2]
, return [2]
.
Note:
【解答】求交集。用HashSet就好了,既可以找交集,又可以去重。
public class Solution { public int[] intersection(int[] nums1, int[] nums2) { if (nums1==null || nums2==null) throw new IllegalArgumentException(); Set<Integer> set2 = new HashSet<>(); for (int n : nums2) { set2.add(n); } Set<Integer> set3 = new HashSet<>(); for (int n : nums1) { if (set2.contains(n)) set3.add(n); } int[] intersection = new int[set3.size()]; int i=0; for (int n : set3) { intersection[i] = n; i++; } return intersection; } }
【题目】 Given a non-empty array of integers, return the k most frequent elements.
For example,
Given
[1,1,1,2,2,3]
and k = 2, return
[1,2]
.
Note:
【解答】从一堆数中寻找k个出现次数最多的,首先出现在大脑里的思路是,先用一个map记录下每个元素的出现次数,然后再排序一下,这样复杂度就是n*logn。下面我用的是TreeMap(红黑树)的做法,TreeMap本身提供的是具有排序key的map这样的功能,它的put/get的时间复杂度都是log n,因而对于n个元素这样的情况来看,理论上的时间复杂度量级依然是n*logn。因此兴许它的时间开销会比map记录下所有元素出现次数以后,再排序一下这种方法好一些,也通过了所有的测试用例,但依然是同一级别的。也就是说,并不能算是满足题目note里面的要求使用比O(n log n)更好的复杂度,欢迎提供更佳的解法。
public class Solution { public List<Integer> topKFrequent(int[] nums, int k) { if (nums==null || nums.length==0 || k<=0) return new ArrayList<>(); Map<Integer, Integer> map = new HashMap<>(); for (int num : nums) { if (map.get(num) == null) map.put(num, 0); map.put(num, map.get(num)+1); } TreeMap<Integer, Set<Integer>> reverseMap = new TreeMap<>(); for (Map.Entry<Integer, Integer> entry : map.entrySet()) { if (reverseMap.get(entry.getValue()) == null) reverseMap.put(entry.getValue(), new HashSet<>()); Set<Integer> set = reverseMap.get(entry.getValue()); set.add(entry.getKey()); reverseMap.put(entry.getValue(), set); } List<Integer> ret = new ArrayList<>(k); while(k>0) { Integer key = reverseMap.lastKey(); if (key==null) break; Set<Integer> set = reverseMap.get(key); reverseMap.remove(key); if (set.size()<=k) { ret.addAll(set); k -= set.size(); } else { for (int s : set) { ret.add(s); k--; if (k==0) break; } } } return ret; } }
【题目】 Write a function that takes a string as input and reverse only the vowels of a string.
Example 1:
Given s = “hello”, return “holle”.
Example 2:
Given s = “leetcode”, return “leotcede”.
Note:
The vowels does not include the letter “y”.
【解答】要把元音反转。思路就是准备两个指针,一个从前往后,一个从后往前,找到元音就互换位置。
public class Solution { public String reverseVowels(String s) { if (null==s) return s; int i=0, j=s.length()-1; char[] arr = s.toCharArray(); while (i<j) { if (!isVowel(arr[i])) { i++; continue; } if (!isVowel(arr[j])) { j--; continue; } swap(arr, i, j); i++; j--; } return new String(arr); } private boolean isVowel(char c) { return c=='a' || c=='e' || c=='i' || c=='o' || c=='u' || c=='A' || c=='E' || c=='I' || c=='O' || c=='U'; } private void swap(char[] arr, int i, int j) { arr[i] ^= arr[j]; arr[j] ^= arr[i]; arr[i] ^= arr[j]; } }
【题目】 Write a function that takes a string as input and returns the string reversed.
Example:
Given s = “hello”, return “olleh”.
【解答】这个题没有太多可说的。
public class Solution { public String reverseString(String s) { if (null==s) return null; int len = s.length(); char[] arr = new char[len]; for (int i=len-1; i>=0; i--) { arr[len-i-1] = s.charAt(i); } return new String(arr); } }
【题目】 Given a positive integer n , break it into the sum of at least two positive integers and maximize the product of those integers. Return the maximum product you can get.
For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).
Note : You may assume that n is not less than 2 and not larger than 58.
【解答】把一个数拆分成若干个数的和,最后要求使得这几个数的乘积最大。
现在假设这个数n,需要拆成k个数,那么一点是肯定的,一旦k被确定下来,这几个数越平均越好。因为拆出来的这几个数,越是接近,乘积就越大。
因此考虑所有k的可能,拆数的时候,n/k未必能够整除,但是能够得到一个“基数”,即这种最平均的拆分法,拆出来的数要么是n/k向下取整,要么是n/k向下取整+1,也就是说任意两个数之间的差别不会超过1。
public class Solution { public int integerBreak(int n) { int max = -1; for (int k=2; k<=n; k++) { int res = cal(n, k); if (res>max) max = res; else return max; } return max; } private int cal(int n, int k) { int c = n/k; int adding = (n - c*k); int result = 1; if (c==0) return -1; for (int i=1; i<=k; i++) { if (adding>0) { adding--; result *= c+1; } else { result *= c; } } return result; } }
【题目】 Given an integer (signed 32 bits), write a function to check whether it is a power of 4.
Example:
Given num = 16, return true. Given num = 5, return false.
Follow up : Could you solve it without loops/recursion?
【解答】要看一个数是不是4的幂。做法有很多,利用一些数的性质可以简化计算:
(1)如果一个数num是2的幂,那它应该是这样的数:
1 -> 1
2 -> 10
4 -> 100
8 -> 1000
……
因此 num & (num-1) 应该得到0。
(2)考虑这样的因数分解:
4^n – 1 = (4-1) (4^(n-1) + 4^(n-2) + 4^(n-3) + … + 4 + 1)
因此取3的模应该为0。
我觉得这二个条件的必要性应该是正确的,但是惭愧的是充分性我并没有证明出来。即,上面的(1)和(2)可以说是“一个数是4的幂”的必要条件,但是如何证明它们也是充分条件呢?
其实,通过(1)的筛选可以找到符合“2的幂”这个条件,而想想2的幂如果它同时也是4的幂,那肯定能被3整除,这一点已经被(2)说明,现在问题转化为,如何说明2的幂但不为4的幂的时候,是无法被3整除的呢?
如果你知道怎样证明,或者有别的思路,我们可以讨论。
public class Solution { public boolean isPowerOfFour(int num) { if(num<=0) return false; // powers of 2 // 10000000... if ((num & (num-1)) != 0) return false; // 4^n - 1 = (4-1) (4^(n-1) + 4^(n-2) + 4^(n-3) + ... + 4 + 1) if ((num-1) % 3 != 0) return false; return true; } }
【题目】 Given a nested list of integers, implement an iterator to flatten it.
Each element is either an integer, or a list — whose elements may also be integers or other lists.
Example 1:
Given the list [[1,1],2,[1,1]]
,
By calling next repeatedly until hasNext returns false, the order of elements returned by next should be: [1,1,2,1,1]
.
Example 2:
Given the list [1,[4,[6]]]
,
By calling next repeatedly until hasNext returns false, the order of elements returned by next should be: [1,4,6]
.
【解答】使用一个栈stack来存放外层的链表节点。每次调用next的时候,都从stack里面pop一个元素出来,然后让它的index往后走。不过如果走不了了,即已经全部遍历过了,那就只能出栈了。
有一个注意的地方是对于hasNext方法的设计。我的做法是在hasNext方法的时候把下一个元素应该返回什么的工作做好,并且使用一个标识量checked表示是否已经通过hasNext检查过了,这样在调用next方法的时候就不用做
class Item { List<NestedInteger> list; int index; } public class NestedIterator implements Iterator<Integer> { private boolean checked; private Integer nextVal; private Stack<Item> stack = new Stack<>(); public NestedIterator(List<NestedInteger> nestedList) { checked = false; nextVal = null; Item item = new Item(); item.list = nestedList; item.index = 0; stack.push(item); } private void check() { if (stack.isEmpty()) { checked = true; nextVal = null; return; } Item item = stack.peek(); if (item.list.size()<=item.index) { stack.pop(); this.check(); } else { NestedInteger nestedInteger = item.list.get(item.index); item.index++; if (nestedInteger.isInteger()) { nextVal = nestedInteger.getInteger(); checked = true; } else { Item subItem = new Item(); subItem.list = nestedInteger.getList(); subItem.index = 0; stack.push(subItem); this.check(); } } } @Override public Integer next() { if (!checked) this.check(); return nextVal; } @Override public boolean hasNext() { this.check(); return nextVal != null; } }
【题目】 Given a non negative integer number num . For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1′s in their binary representation and return them as an array.
Example:
For num = 5
you should return [0,1,1,2,1,2]
.
Follow up:
【解答】要数1的个数,再看题目里对复杂度的要求,对1到n的每一个数拿出来,必须要在常数时间复杂度内就得出结果。因此考虑1到n中每一个数和它之前的数之间的关系,利用之前的数已经算得的结果(存在数组array中),再加上变化的增量,以减少计算量。
array[0] = 0,1的数量:0
array[1] = 1 -> 2^0 -> 1,1的数量:1
array[2] = 2 -> 2^1 -> 10,1的数量:array[0] + 1
array[3] = 3 -> 2^1 + 1 -> 11,1的数量:array[1] + 1
array[4] = 4 -> 2^2 -> 100,1的数量:array[0] + 1
array[5] = 5 -> 2^2 + 1 -> 101,1的数量:array[1] + 1
array[6] = 6 -> 2^2 + 2 -> 110,1的数量:array[2] + 1
array[7] = 7 -> 2^2 + 3 -> 111,1的数量:array[3] + 1
array[8] = 8 -> 2^3 -> 1000,1的数量:array[0] + 1
在纸上写出如上,寻找思路:
public class Solution { public int[] countBits(int num) { if (num==0) return new int[]{0}; else if (num==1) return new int[]{0, 1}; int cycleCount = 1; int pos = 0; int[] array = new int[num+1]; array[0] = 0; array[1] = 1; int cycleLength = (int)Math.pow(2, cycleCount); for (int i=2; i<array.length; i++) { array[i] = array[pos] + 1; pos++; if (pos>=cycleLength) { pos = 0; cycleCount++; cycleLength = (int)Math.pow(2, cycleCount); } } return array; } }
【题目】 The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the “root.” Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that “all houses in this place forms a binary tree”. It will automatically contact the police if two directly-linked houses were broken into on the same night.
Determine the maximum amount of money the thief can rob tonight without alerting the police.
Example 1:
3 / / 2 3 / / 3 1
Maximum amount of money the thief can rob = 3 + 3 + 1 = 7 .
Example 2:
3 / / 4 5 / / / 1 3 1
Maximum amount of money the thief can rob = 4 + 5 = 9 .
【解答】这道题思路相对还是比较简单的。建立两个map,一个用来存放指定节点被抢劫的最优解,一个用来存放指定节点不被抢劫的最优解。
然后就可以从root开始递归遍历,注意递归方法需要传入一个boolean变量表示在某种情况下,这次的递归调用所对应的节点是否可以被抢劫:
public class Solution { public int rob(TreeNode root) { return rob(root, true, new HashMap<>(), new HashMap<>()); } private int rob(TreeNode root, boolean robbable, Map<TreeNode, Integer> robbedCache, Map<TreeNode, Integer> unrobbedCache) { if (root==null) return 0; int robbed = 0; if (robbable) { if (robbedCache.get(root) != null) { robbed = robbedCache.get(root); } else { robbed += root.val; robbed += rob(root.left, false, robbedCache, unrobbedCache); robbed += rob(root.right, false, robbedCache, unrobbedCache); robbedCache.put(root, robbed); } } int unrobbed = 0; if (unrobbedCache.get(root) != null) { unrobbed = unrobbedCache.get(root); } else { unrobbed += rob(root.left, true, robbedCache, unrobbedCache); unrobbed += rob(root.right, true, robbedCache, unrobbedCache); unrobbedCache.put(root, unrobbed); } return Math.max(robbed, unrobbed); } }
【题目】 Given a list of unique words, find all pairs of distinct indices (i, j)
in the given list, so that the concatenation of the two words, i.e. words[i] + words[j]
is a palindrome.
Example 1:
Given words
= ["bat", "tab", "cat"]
Return [[0, 1], [1, 0]]
The palindromes are ["battab", "tabbat"]
Example 2:
Given words
= ["abcd", "dcba", "lls", "s", "sssll"]
Return [[0, 1], [1, 0], [3, 2], [2, 4]]
The palindromes are ["dcbaabcd", "abcddcba", "slls", "llssssll"]
【解答】从一堆单词里面找出两个,连接在一起组成回文,要列出所有这样的可能性。
最暴力的解法就是两两组合,考虑所有的可能性,但是复杂度在n平方。
因此思考改进的方式。两个单词连接,如果较短的那一个已经确定,再去取较长的那一个的时候,满足如下条件:
对于长串和短串的关系,具体说,有两种情况:
明确这个关系以后,改进的方式就在心中浮现了:如果拿到任意一个串,视之为短串,能够不需要遍历剩余所有的串,而比较快速地得到以他的反串为前缀或者后缀的对应长串,再来判断长串减去短串后余下部分是否是回文即可。
对下面的代码做一个简要说明,首先构造如下的数据结构:
构造以上结构完成之后,以这样的输入来举例:[ae, ab, bac, ca]。
class Node { Character ch; Map<Character, Node> nextMap = new HashMap<>(); String word; public Node(Character ch) { this.ch = ch; } @Override public String toString() { return "/"" + word + "/"-" + nextMap; } } public class Solution { public List<List<Integer>> palindromePairs(String[] words) { Map<String, Integer> stringToIndex = new HashMap<>(); Map<String, String> toReversed = new HashMap<>(); Map<String, String> toOriginal = new HashMap<>(); // key: reversed word, value: end node in the word letters iteration tree Map<String, Node> stringToNodeForward = new HashMap<>(); // key: word, value: end node in the word letters backward iteration tree Map<String, Node> stringToNodeBackward = new HashMap<>(); Node forwardRoot = new Node(null); Node backwardRoot = new Node(null); for (int i=0; i<words.length; i++) { String word = words[i]; stringToIndex.put(word, i); // forward Node cursor = forwardRoot; for (int j=0; j<word.length(); j++) { Character ch = word.charAt(j); if (!cursor.nextMap.containsKey(ch)) cursor.nextMap.put(ch, new Node(ch)); cursor = cursor.nextMap.get(ch); } String reverse = new StringBuilder(word).reverse().toString(); toReversed.put(word, reverse); toOriginal.put(reverse, word); cursor.word = word; stringToNodeForward.put(reverse, cursor); // backward cursor = backwardRoot; for (int j=word.length()-1; j>=0; j--) { Character ch = word.charAt(j); if (!cursor.nextMap.containsKey(ch)) cursor.nextMap.put(ch, new Node(ch)); cursor = cursor.nextMap.get(ch); } cursor.word = word; stringToNodeBackward.put(word, cursor); } List<List<Integer>> list = new ArrayList<>(); for (String word : words) { // case 1 String reverse = toReversed.get(word); Node node = getNode(reverse, forwardRoot); if (node != null) { Set<String> set = new HashSet<>(); recursiveSearch(node, set, word.length(), true); for (String p : set) { List<Integer> item = new ArrayList<>(2); item.add(stringToIndex.get(p)); item.add(stringToIndex.get(word)); if (item.get(0) != item.get(1)) // filter out the map to itself list.add(item); } } // case 2 node = getNode(word, backwardRoot); if (node != null) { Set<String> set = new HashSet<>(); recursiveSearch(node, set, word.length(), false); for (String p : set) { List<Integer> item = new ArrayList<>(2); item.add(stringToIndex.get(word)); item.add(stringToIndex.get(p)); if (item.get(0) != item.get(1)) // filter out the map to itself list.add(item); } } } return list; } private Node getNode(String s, Node root) { Node n = root; for (int i=0; i<s.length(); i++) { Character ch = s.charAt(i); Node next = n.nextMap.get(ch); if (next != null) n = next; else return null; } return n; } private void recursiveSearch(Node node, Set<String> set, int baseLength, boolean forward) { if (node == null) return; if (node.word != null) { String rest = null; if (forward) rest = node.word.substring(baseLength); else if (node.word.length() != baseLength) // dedup rest = node.word.substring(0, node.word.length() - baseLength); // if the rest is palindrome if (rest != null && new StringBuilder(rest).reverse().toString().equals(rest)) set.add(node.word); } for (Node next : node.nextMap.values()) recursiveSearch(next, set, baseLength, forward); } }
【题目】 You are given an array x of n
positive numbers. You start at point (0,0)
and moves x[0]
metres to the north, then x[1]
metres to the west, x[2]
metres to the south, x[3]
metres to the east and so on. In other words, after each move your direction changes counter-clockwise.
Write a one-pass algorithm with O(1)
extra space to determine, if your path crosses itself, or not.
Example 1:
Given x = [2, 1, 1, 2]
,
┌───┐
│ │
└───┼──>
│
Return true (self crossing)
Example 2:
Given x = [1, 2, 3, 4]
,
┌──────┐
│ │
│
│
└────────────>
Return false (not self crossing)
Example 3:
Given x = [1, 1, 1, 1]
,
┌───┐
│ │
└───┼>
Return true (self crossing)
【解答】题目本身很清晰,这道题hard难度,我确实折腾了好一会儿。我发现LeetCode的题目是越来越难做了,是我思维退步了还是出题人心理越来越阴暗了?……
用的还是分类case来归纳讨论的思路。虽然结果通过了,但是我并非很有信心,因为这里的case实在是折腾得我有点思路混乱。无论如何,我把我的解法放在了下面。
首先,大类上可以归纳为这样三种,我觉得这点是不会有错的:
一旦收缩以后就无法再膨胀了,因此没有“先收缩再膨胀”这种类型。下面代码的shrink变量表示是收缩还是膨胀,而inflateToShrink变量用来表示从膨胀到收缩那个临界变化的位置,在那个位置有很多特殊的case。
public class Solution { public boolean isSelfCrossing(int[] x) { if (x==null || x.length<4) return false; boolean shrink; if (x[2] <x[0]) shrink = true; else if (x[2] > x[0]) shrink = false; else if (x[3] < x[1]) shrink = true; else if (x[3] > x[1]) shrink = false; else return true; // rectangle boolean inflateToShrink = false; for (int i=3; i<x.length; i++) { if (shrink) { if (x[i] > x[i-2]) // 1 return true; else if (x[i] == x[i-2] && x[i-1] == x[i-3]) // 2 return true; else // 3 ; } else { if (x[i] == x[i-2] && x[i-1] == x[i-3]) { // 4 return true; } else if (i == 3) { if (x[i] == x[i-2]) // 5 inflateToShrink = true; else if (x[i] < x[i-2]) // 6 shrink = true; else // 7 ; } else if (x[i] < x[i-2]) { if (x[i] + x[i-4] < x[i-2]) { // 8 shrink = true; } else { if (inflateToShrink) // 9 return true; else // 10 inflateToShrink = true; } } else { // 11 ; } } } return false; } }
在 讨论区 有非常清晰简洁的解法,我就不贴在这里了,也是分类讨论的办法,但是清晰很多,而且不需要状态变量。兴许这一类题目有某种思路简单的套路可以采用呢?
【题目】 Given an unsorted array return whether an increasing subsequence of length 3 exists or not in the array.
Formally the function should:
Return true if there exists i, j, k
such that arr[i] < arr[j] < arr[k] given 0 ≤ i < j < k ≤ n -1 else return false.
Your algorithm should run in O( n ) time complexity and O( 1 ) space complexity.
Examples:
Given [1, 2, 3, 4, 5]
,
return true
.
Given [5, 4, 3, 2, 1]
,
return false
.
【解答】要找出三个数,随着下标增加,数的大小也是递增的。这个问题其实通过仔细的分类讨论就可以解决。
参见下面的代码,核心逻辑是:
public class Solution { public boolean increasingTriplet(int[] nums) { if (nums==null || nums.length<3) return false; int first = 0, second = -1, third = -1; // if there is already first and second number found (nums[first]<nums[second]), // and if another number x occurs and x < nums[first], // then we don't know if x should be put in the triplet if there is, so we put its index in a "firstBackup" variable temporarily int firstBackup = -1; for (int i=1; i<nums.length; i++) { // increasing if (nums[i]>nums[i-1]) { if (second==-1) { second = i; } else { if (nums[i]>nums[second]) return true; else second = i; } } // decreasing else { if (second==-1) { if (nums[i]<=nums[first]) first = i; else second = i; } else { if (nums[i]>nums[second]) { return true; // unreachable, impossible case } else { if (nums[i]>nums[first]) { if (firstBackup != -1) { first = firstBackup; firstBackup = -1; } second = i; } else { if (firstBackup==-1 || nums[i]<firstBackup) firstBackup = i; else ; // ignore } } } } } return false; } }
【题目】 Given a list of airline tickets represented by pairs of departure and arrival airports [from, to]
, reconstruct the itinerary in order. All of the tickets belong to a man who departs from JFK
. Thus, the itinerary must begin with JFK
.
Note:
["JFK", "LGA"]
has a smaller lexical order than ["JFK", "LGB"]
. Example 1:
tickets
=
[["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]]
Return ["JFK", "MUC", "LHR", "SFO", "SJC"]
.
Example 2:
tickets
=
[["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
["JFK","ATL","JFK","SFO","ATL","SFO"]
.
Another possible reconstruction is
["JFK","SFO","ATL","JFK","ATL","SFO"]
. But it is larger in lexical order.
【解答】寻找一个以JFK为首的连续路径,要求把机票都用完,每张机票只用一次。
一开始思路是,使用一个名为visited的map来存储机票是不是用过了,然后使用一个value为list的map来存储出发点和终点之间的对应关系。接着从JFK出发,遍历所有可能性。写代码的时候就觉得有些忐忑,因为看起来整个方法平平淡淡,并不优雅,结果不出意外地超时了。
public class Solution { public List<String> findItinerary(String[][] tickets) { List<String> result = new ArrayList<>(); if (null==tickets) return null; else if(tickets.length==0) return result; Map<String, Boolean> visited = new HashMap<>(); Map<String, List<String>> paths = new HashMap<>(); for (String[] pair : tickets) { if (!paths.containsKey(pair[0])) paths.put(pair[0], new ArrayList<String>()); paths.get(pair[0]).add(pair[1]); visited.put(pair[0]+pair[1], false); } for (List<String> list : paths.values()) { Collections.sort(list); } String key = "JFK"; result.add(key); if (traverse(paths, visited, key, result)) return result; else return null; } private boolean traverse(Map<String, List<String>> paths, Map<String, Boolean> visited, String key, List<String> result) { if (result.size()-1 == visited.size()) return true; List<String> list = paths.get(key); if (list==null) return false; for (String val : list) { String visitedKey = key+val; if (!visited.get(visitedKey)) { visited.put(visitedKey, true); result.add(val); if (traverse(paths, visited, val, result)) { return true; } else { result.remove(result.size()-1); visited.put(visitedKey, false); continue; } } } return false; } }
如何简化?其实和下面的那道二叉树先序遍历的题类似,图和路径的问题与树一样,也可以考虑使用入度和出度之间的关系来简化。
下面的简明无比的思路来自 这里 ,核心是根据入度和出度的关系,入口的入度-1=出度,而出口的入度+1=出度。
还是使用map来存储出发点和终点的对应关系,但是使用优先级队列来存储终点列表,以免去手动sort之苦。
在下面的递归调用visit的方法中,传入当前所在的地点,只要map中还包含,就从它对应的终点中poll一个出来继续visit,以此while不断进行,直到无法进行下去为止。为什么有了递归还需要这个while?因为一个地点有可能遍历到好几次,即有多张机票的起点相同。
但是形成最终路径的时候,要在while循环之后,在已有路径的头部插入当前地点,因为当前地点是已生成路径的出发点。
// All nodes except entrance and exit should have the same indegree and outdegree, // if a node indegree-1 == outdegree, it's the exit, and it's also the exit of "while" and the recursion // // use PriorityQueue so there's no need to sort the list explicitly public class Solution { private Map<String, PriorityQueue<String>> targets = new HashMap<>(); private List<String> route = new LinkedList<String>(); public List<String> findItinerary(String[][] tickets) { for (String[] pair : tickets) { if (targets.get(pair[0]) == null) targets.put(pair[0], new PriorityQueue<String>()); targets.get(pair[0]).add(pair[1]); } this.visit("JFK"); return route; } private void visit(String airport) { while(targets.containsKey(airport) && !targets.get(airport).isEmpty()) this.visit(targets.get(airport).poll()); route.add(0, airport); } }
【题目】 One way to serialize a binary tree is to use pre-order traversal. When we encounter a non-null node, we record the node’s value. If it is a null node, we record using a sentinel value such as #
.
_9_ / / 3 2 / / / / 4 1 # 6 / / / / / / # # # # # #
For example, the above binary tree can be serialized to the string "9,3,4,#,#,1,#,#,2,#,6,#,#"
, where #
represents a null node.
Given a string of comma separated values, verify whether it is a correct preorder traversal serialization of a binary tree. Find an algorithm without reconstructing the tree.
Each comma separated value in the string must be either an integer or a character '#'
representing null
pointer.
You may assume that the input format is always valid, for example it could never contain two consecutive commas such as "1,,3"
.
Example 1:
"9,3,4,#,#,1,#,#,2,#,6,#,#"
Return true
Example 2:
"1,#"
Return false
Example 3:
"9,#,#,1"
Return false
【解答】开始的思路是,既然是先序遍历,那给定串的第一个数肯定是root,剩下的部分拆分成左子树和右子树,考虑所有拆分可能,并且用一个布尔型二维数组来表示一段区间是否是合法的二叉树以避免重复计算:
class Solution { public boolean isValidSerialization(String preorder) { if (preorder==null) return false; String[] arr = preorder.split(","); // (from, to] return verify(arr, 0, arr.length, new Boolean[arr.length][arr.length]); } private boolean verify(String[] arr, int from, int to, Boolean[][] cache) { if (from==to) { return true; } else if (from+2==to) { return false; } else if (from+1==to) { if ("#".equals(arr[from])) return true; else return false; } if (cache[from][to-1] != null) return cache[from][to-1]; String root = arr[from]; if ("#".equals(root)) { cache[from][to-1] = false; return false; } for (int i=from+2; i<to; i++) { // a tree must be ended with "#" if (!"#".equals(arr[i-1])) continue; boolean left = verify(arr, from+1, i, cache); if (!left) continue; boolean right = verify(arr, i, to, cache); if (right) { cache[from][to-1] = true; return true; } } cache[from][to-1] = false; return false; } }
这个思路确实容易想到,可是很快超时了。在 讨论区 我看到了一种巧妙的办法,而且具有一定的典型性,对于树的问题可以通过利用入度和出度之间的关系来简化计算:
对于每一个节点,我们认定从父节点来的路径为入,通往子节点的路径为出,
有了这样的关系,我们就可以沿着先序遍历的顺序从root开始挨个遍历,使用diff来跟踪当前(入度-出度)的值,并且给定一个初始补偿值1,这就意味着,如果这个先序遍历是合法的,那么整个便利过程中,应该不会出现diff小于0的情况,并且,在遍历完成后,diff应该等于0。
这个解法的复杂度只有O(n),而且不用考虑使用额外占用空间复杂度的布尔数组来存储中间值。
class Solution { public boolean isValidSerialization(String preorder) { String[] nodes = preorder.split(","); int diff = 1; for (String node: nodes) { if (--diff < 0) return false; if (!node.equals("#")) diff += 2; } return diff == 0; } }
【题目】 Given a sorted positive integer array nums and an integer n , add/patch elements to the array such that any number in range [1, n]
inclusive can be formed by the sum of some elements in the array. Return the minimum number of patches required.
Example 1:
nums = [1, 3]
, n = 6
Return 1
.
[1], [3], [1,3]
, which form possible sums of:
1, 3, 4
.
Now if we add/patch
2
to nums , the combinations are:
[1], [2], [3], [1,3], [2,3], [1,2,3]
.
Possible sums are
1, 2, 3, 4, 5, 6
, which now covers the range
[1, 6]
.
So we only need
1
patch.
[1, 5, 10]
, n =
20
2
.
The two patches can be
[2, 4]
.
Example 3:
nums = [1, 2, 2]
, n = 5
Return 0
.
【解答】要给一个升序正整数数组打补丁,即添加上若干个数以后,使得使用这些数能够相加组合出从1到n所有的值来。
如果按照正向的思路去想,当前这个正整数组能够产生在1到n之间的哪些数,还差哪些数,添加怎样的数去覆盖这些差的数,就会走入死路,非常难解。但是如果从另外一个角度去看这个问题,问题就好办很多。把1到n这些数从小到大挨个拿出来看,第一个数能不能生成,第二个数能不能生成……一直到第n个:
好,考虑这样的递推关系:当前[1, max]是可以被覆盖的,那么下面我要从nums中去取下一个数,
上面这一步的思考非常重要,想到的话基本上解题就顺理成章了。
那么如果打了max+1这个patch,新的覆盖范围会变成多少呢?原来的范围是[1,max],现在有了max+1,这就意味着原来的范围中的每一个数都加上max+1也可以生成了,即可以覆盖[1+(max+1), max+(max+1)],再加上原来就已经覆盖的[1,max],于是最新的覆盖范围是[1, max+(max+1)]。
public class Solution { public int minPatches(int[] nums, int n) { // the index indicates [1, x] needs to be covered; use long to avoid overflow long x = 1; // the index in nums int index = 0; // for now [1, max] can be covered long max = 0; int patch = 0; while (x<=n) { // a new number is needed to cover x if (max<x) { // no more existing number, or the next number can't cover max+1 if (index>=nums.length || nums[index]>max+1) { // patch needed, and the patch number is max+1 max = max + (max+1); patch++; } else { // the new range from max to (max+nums[index]) can be covered because // before now from 1 to max can be covered, and now there's a new number nums[index] // so adding nums[index] to previous range [1, max] we'll get a new covered range [nums[index]+1, nums[index]+max], // union both above to get the range [1, max+nums[index]] max = max + nums[index]; index++; } } else { // no new number needed x = max+1; } } return patch; } }
【题目】 Given an integer matrix, find the length of the longest increasing path.
From each cell, you can either move to four directions: left, right, up or down. You may NOT move diagonally or move outside of the boundary (i.e. wrap-around is not allowed).
Example 1:
nums = [ [9,9,4], [6,6,8], [2,1,1] ]
Return 4
The longest increasing path is [1, 2, 6, 9]
.
Example 2:
nums = [ [3,4,5], [3,2,6], [2,2,1] ]
Return 4
The longest increasing path is [3, 4, 5, 6]
. Moving diagonally is not allowed.
【解答】这道题比较简单。要找出最长的递增路径。基本思路是尝试以每一个点为起点去遍历寻找,但是如果已经找过的部分,就不要重复找了。因此用一个map来存储这个已经找过的结果,lenMap[i][j]表示以matrix[i][j]为起点的最长路径的长度。
public class Solution { public int longestIncreasingPath(int[][] matrix) { if (matrix==null) return 0; int h = matrix.length; if (h==0) return 0; int w = matrix[0].length; if (w==0) return 0; int[][] lenMap = new int[h][w]; int max = 0; for (int i=0; i<h; i++) { for (int j=0; j<w; j++) { max = Math.max(max, getLen(matrix, i, j, lenMap)); } } return max; } private int getLen(int[][] matrix, int i, int j, int[][] lenMap) { int h = matrix.length; int w = matrix[0].length; if (i<0 || i>=h || j<0 || j>=w) return 0; if (lenMap[i][j]>0) return lenMap[i][j]; int len = 1; if (i+1<h && matrix[i][j]<matrix[i+1][j]) { int down = getLen(matrix, i+1, j, lenMap); len = Math.max(len, down+1); } if (j+1<w && matrix[i][j]<matrix[i][j+1]) { int right = getLen(matrix, i, j+1, lenMap); len = Math.max(len, right+1); } if (i-1>=0 && matrix[i][j]<matrix[i-1][j]) { int up = getLen(matrix, i-1, j, lenMap); len = Math.max(len, up+1); } if (j-1>=0 && matrix[i][j]<matrix[i][j-1]) { int left = getLen(matrix, i, j-1, lenMap); len = Math.max(len, left+1); } lenMap[i][j] = len; return lenMap[i][j]; } }
【题目】 Given a singly linked list, group all odd nodes together followed by the even nodes. Please note here we are talking about the node number and not the value in the nodes.
You should try to do it in place. The program should run in O(1) space complexity and O(nodes) time complexity.
1->2->3->4->5->NULL
,
return
1->3->5->2->4->NULL
.
The relative order inside both the even and odd groups should remain as it was in the input.
The first node is considered odd, the second node even and so on …
【解答】题目本身没有什么特别的,只不过要求O(1)的空间复杂度,这就意味着基本上只能用简单的指针来解决,不能用辅助栈或者辅助数组。题目比较简单,注意对奇数链表和偶数链表头和尾的把握。
public class Solution { public ListNode oddEvenList(ListNode head) { ListNode odd = new ListNode(0); ListNode tailOdd = odd; ListNode even = new ListNode(0); ListNode tailEven = even; boolean isOdd = true; ListNode cur = head; while (cur != null) { if (isOdd) { tailOdd.next = cur; tailOdd = cur; } else { tailEven.next = cur; tailEven = cur; } cur = cur.next; isOdd = !isOdd; } tailOdd.next = even.next; tailEven.next = null; return odd.next; } }
【题目】 Given an integer array nums
, return the number of range sums that lie in [lower, upper]
inclusive.
Range sum S(i, j)
is defined as the sum of the elements in nums
between indices i
and j
( i
≤ j
), inclusive.
Note:
A naive algorithm of O ( n2
) is trivial. You MUST do better than that.
Example:
Given nums = [-2, 5, -1]
, lower = -2
, upper = 2
,
Return 3
.
The three ranges are : [0, 0]
, [2, 2]
, [0, 2]
and their respective sums are: -2, -1, 2
.
【解答】要找数组满足条件的区间数,要求区间内数之和在i和j之间。确实很容易想到n平方的解法,很清晰也很简单,但是如果要进一步优化时间复杂度,数组并非有序,就不是那么好办了。于是思路开始自然地往动态规划上面靠,可是怎样减少计算量呢?任意一个区间的和,可以由它邻近区间的一个已知结果加减差异元素来得出,可是这样的算法依然要求遍历每一个区间的可能,并未减小复杂度的量级。
这个问题的常规解法有两种,第一种是树状数组(Fenwick tree,或者Binary Indexed Tree – BIT),对于它这个问题是很有代表性的。它用来解决的问题是数组元素更新和连续的N个数求和之间性能开销的平衡问题,换言之,如果我总是需要对某个数组的元素进行更新,而更新以后我有需要得到它任意区间和的最新值,那么用树状数组往往可以获得比较好的性能。定义上不是那么直观,如果原数组是array,树状数组BIT元素的通式为:BIT[i] = array[i–2^k+ 1] + … + array[i],其中2^k=i&(i^(i-1)),即i&(-i)。但是转换成图示可能会容易理解一点,下面这张图来自 维基百科 ,非常形象地说明了树状数组的构件过程:有这样一个数组[1,2,3,4,5],从左往右每次取一个元素,每一个BIT的元素都代表了其中一段的和。这样在扫描数组的时候,可以一段一段扫描,而不是像普通数组那样一个一个数地扫描;在更新的时候,也只需要遍历从BIT树的叶子节点一路往上走,直到根为止,没有遍历到的节点不需要更新。这个复杂度就可以降到nlogn。
下面这张图(来自 这里 )更容易看出每一个BIT元素所代表的哪一段的区间和:
遗憾的是,BIT树在JDK里面没有实现,因此即便好不容易想到了用它来解,还需要自己去实现( 参考实现 )BIT,这就让这道题变成hard难度。
另一种解法来自这个讨论中的 高票回答 ,不需要BIT的知识,却巧妙借用归并排序的思想,二分归并这个行为本身的复杂度log n,乘以每一个子数组遍历的复杂度n,因此复杂度就是nlogn。如果我们能对原数组进行排序,这样就可以利用二分查找的方法把复杂度降下来,但是因为我们需要找的是区间的和,区间意味着连续的数串,因此对原数组排序是不可行的。但是,如果我们定义一个和数组sums,sums[i]表示的是原数组签名i个元素的和,那么我们就可以对sums[i]来进行归并排序,这一思路是解题的关键:
public class Solution { public int countRangeSum(int[] nums, int lower, int upper) { int n = nums.length; long[] sums = new long[n + 1]; for (int i = 0; i < n; ++i) sums[i + 1] = sums[i] + nums[i]; return countWhileMergeSort(sums, 0, n + 1, lower, upper); } private int countWhileMergeSort(long[] sums, int start, int end, int lower, int upper) { if (end - start <= 1) return 0; int mid = (start + end) / 2; int count = countWhileMergeSort(sums, start, mid, lower, upper) + countWhileMergeSort(sums, mid, end, lower, upper); int j = mid, k = mid, t = mid; long[] cache = new long[end - start]; for (int i = start, r = 0; i < mid; ++i, ++r) { while (k < end && sums[k] - sums[i] < lower) k++; while (j < end && sums[j] - sums[i] <= upper) j++; while (t < end && sums[t] < sums[i]) cache[r++] = sums[t++]; cache[r] = sums[i]; count += j - k; } System.arraycopy(cache, 0, sums, start, t - start); return count; } }
【题目】 Given an integer, write a function to determine if it is a power of three.
Follow up:
Could you do it without using any loop / recursion?
【解答】其实3的乘方数很少,因为乘方的大小上涨得很快,所以完全可以在初始化的时候就找出所有3的乘方数来。当心溢出就好。
public class Solution { private Set<Integer> all = new HashSet<>(); public Solution() { int upperLimit = Integer.MAX_VALUE/3; int n=1; while(true) { all.add(n); if (n<upperLimit) n = n*3; else break; } } public boolean isPowerOfThree(int n) { return all.contains(n); } }
【题目】 Given an unsorted array nums
, reorder it such that nums[0] < nums[1] > nums[2] < nums[3]...
.
Example:
(1) Given nums = [1, 5, 1, 1, 6, 4]
, one possible answer is [1, 4, 1, 5, 1, 6]
.
(2) Given nums = [1, 3, 2, 2, 3, 1]
, one possible answer is [2, 3, 1, 3, 1, 2]
.
Note:
You may assume all input has valid answer.
Follow Up:
Can you do it in O(n) time and/or in-place with O(1) extra space?
【解答】要最终排成一大一小依次排列的结果。我的做法是:
要着重说明的一点就是,上面这个过程,遍历都是逆序的。也就是说,排好序之后从左到右是从小到大,但是取数的时候是从右到左,即从大到小来取的。
为什么要这样做?
因为排序以后的数组是从小到大的,因此第一遍拿大的一半,那么最大的数会在结果数组靠左的位置,而原数组中居中的数会在结果数组靠右的位置;第二遍拿小的一半,于是原数组中居中的数会在结果数组靠左的位置,而最小的数会在结果数组中靠右的位置——于是居中的数被拆开了,分别在结果数组的最左边和最右边。这样就避免了重复的数落在了结果数组中相邻的位置。
比如说4,5,5,6:如果按照上面的方法逆序遍历会得到5,6,4,5,但是如果顺序遍历,会得到4,5,5,6,于是问题就出在相邻的两个5上。
// time: O(nlogn), space: O(n) public class Solution { public void wiggleSort(int[] nums) { // clone and sort int[] copy = nums.clone(); Arrays.sort(copy); int index = copy.length-1; // fill in nums with odd index, going backward in array copy: // they're all large numbers for (int i=1; i<nums.length; i+=2) { nums[i] = copy[index]; index--; } // fill in nums with even index, going backward in array copy: // they're all small numbers for (int i=0; i<nums.length; i+=2) { nums[i] = copy[index]; index--; } } }
【题目】 You are given coins of different denominations and a total amount of money amount . Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1
.
Example 1:
coins =[1, 2, 5]
, amount =
11
return 3
(11 = 5 + 5 + 1)
Example 2:
coins =[2]
, amount =
3
return -1
.
Note :
You may assume that you have an infinite number of each kind of coin.
【解答】要求最少钱币的数目。
如果有1、2、5三枚面值不同的硬币,那么对于任意目标值x,我如果能知道(x-1)、(x-2)和(x-5)这三个值所用到的最少钱币数目就好了。
想到这里,就可以用动态规划来解答了。
public class Solution { public int coinChange(int[] coins, int amount) { Arrays.sort(coins); return coin(coins, coins.length-1, amount, new int[coins.length][amount+1]); } private int coin(int[] coins, int pos, int amount, int[][] cache) { if (amount==0) return 0; if (cache[pos][amount]!=0) return cache[pos][amount]; int min = -1; for (int i=pos; i>=0; i--) { if (coins[i]>amount) continue; int count = coin(coins, i, amount-coins[i], cache); if (count!=-1 && (count<min || min==-1)) min = count; } if (min!=-1) min++; cache[pos][amount] = min; return min; } }
【题目】 Given two arrays of length m
and n
with digits 0-9
representing two numbers. Create the maximum number of length k <= m + n
from digits of the two. The relative order of the digits from the same array must be preserved. Return an array of the k
digits. You should try to optimize your time and space complexity.
Example 1:
nums1 = [3, 4, 6, 5]
nums2 = [9, 1, 2, 5, 8, 3]
k = 5
return [9, 8, 6, 5, 3]
Example 2:
nums1 = [6, 7]
nums2 = [6, 0, 4]
k = 5
return [6, 7, 6, 0, 4]
Example 3:
nums1 = [3, 9]
nums2 = [8, 9]
k = 3
return [9, 8, 9]
【解答】给两串数m和n,分别取几个,加起来的个数不超过k,并且从m和n中取的子串保持在原串中顺序相对位置,然后任意位置merge起来。要求最后得到的那一串数所代表的那个数尽可能大。
看起来,这道题给人的第一印象是,从m中选一串数,从n中也选一串数,这两个是变化点。但是如果这两串数定下来,那么下面的行为类似于归并排序,每一个子串都有一个指针记录当前位置,从这两个子串中取数每次都取尽量大的数。
顺着这样的思路,我一开始建立了一个比较复杂的模型,一个三维的动态规划。但是我们一般只会接触到最多二维的动态规划,所以当时的感觉就觉得可能走到歪路上去了。果然,还是过于复杂,执行超时了。简单介绍当时的思路。
建立dp[x][y][k]表示第一个子串从m的[x, m.length)中取,第二个子串从n的[y, n.length)中取,那么分析如下四种case:
也就是说,当前的子串状态,可能源于上面四个case,发生变化得到,具体哪一个呢,需要再去比较以上这4个case情况下的大小,取最大的结果……很多数组拷贝,看着就很冗长。
class Item { int[] data; } public class Solution { public int[] maxNumber(int[] nums1, int[] nums2, int k) { Item[][][] dp = new Item[nums1.length+1][nums2.length+1][k+1]; Item result = maxNumber(nums1, nums2, 0, 0, k, dp); return result.data; } private Item maxNumber(int[] nums1, int[] nums2, int x, int y, int k, Item[][][] dp) { if (dp[x][y][k]!=null) return dp[x][y][k]; Item item = new Item(); if (k==0) { item.data = new int[]{}; dp[x][y][k] = item; return item; } if (nums1.length-x + nums2.length-y < k) return null; // case 1: pick from nums1 Item pickNums1 = null; if (x<nums1.length) pickNums1 = buildItem(nums1[x], maxNumber(nums1, nums2, x+1, y, k-1, dp)); // case 2: pick from nums2 Item pickNums2 = null; if (y<nums2.length) pickNums2 = buildItem(nums2[y], maxNumber(nums1, nums2, x, y+1, k-1, dp)); // case 3: pass in nums1 Item passNums1 = null; if (x<nums1.length) passNums1 = maxNumber(nums1, nums2, x+1, y, k, dp); // case 4: pass in nums2 Item passNums2 = null; if (y<nums2.length) passNums2 = maxNumber(nums1, nums2, x, y+1, k, dp); // choose the largest one Item result = max(pickNums1, pickNums2, passNums1, passNums2); dp[x][y][k] = result; return result; } private Item buildItem(int num, Item item) { if (item==null) return null; Item newItem = new Item(); newItem.data = new int[item.data.length+1]; System.arraycopy(item.data, 0, newItem.data, 1, item.data.length); newItem.data[0] = num; return newItem; } private Item max(Item… items) { Item max = null; for (Item item : items) { boolean result = compare(max, item); if (!result) max = item; } return max; } private boolean compare(Item a, Item b) { if (b==null) return true; if (a==null) return false; if (a.data.length > b.data.length) return true; if (a.data.length < b.data.length) return false; for (int i=0; i<a.data.length; i++) { if (a.data[i] > b.data[i]) return true; if (a.data[i] < b.data[i]) return false; } return true; } }
在这个网站上有一个 好的解法 ,我拿过来了。基本思路是:
public class Solution { public int[] maxNumber(int[] nums1, int[] nums2, int k) { int[] ans = new int[k]; for (int i = Math.max(k - nums2.length, 0); i <= Math.min(nums1.length, k); i++) { int[] res1 = get_max_sub_array(nums1, i); int[] res2 = get_max_sub_array(nums2, k - i); int[] res = new int[k]; int pos1 = 0, pos2 = 0, tpos = 0; while (pos1 < res1.length || pos2 < res2.length) { res[tpos++] = greater(res1, pos1, res2, pos2) ? res1[pos1++] : res2[pos2++]; } if (!greater(ans, 0, res, 0)) ans = res; } return ans; } public boolean greater(int[] nums1, int start1, int[] nums2, int start2) { for (; start1 < nums1.length && start2 < nums2.length; start1++, start2++) { if (nums1[start1] > nums2[start2]) return true; if (nums1[start1] < nums2[start2]) return false; } return start1 != nums1.length; } public int[] get_max_sub_array(int[] nums, int k) { int[] res = new int[k]; int len = 0; for (int i = 0; i < nums.length; i++) { while (len > 0 && len + nums.length - i > k && res[len - 1] < nums[i]) { len--; } if (len < k) res[len++] = nums[i]; } return res; } }
【题目】 There are n bulbs that are initially off. You first turn on all the bulbs. Then, you turn off every second bulb. On the third round, you toggle every third bulb (turning on if it's off or turning off if it's on). For the i th round, you toggle every i bulb. For the n th round, you only toggle the last bulb. Find how many bulbs are on after n rounds.
Example:
Given n = 3.
At first, the three bulbs are [off, off, off]. After first round, the three bulbs are [on, on, on]. After second round, the three bulbs are [on, off, on]. After third round, the three bulbs are [on, off, off].
So you should return 1, because there is only one bulb is on.
【解答】如果你和我开始一样,尝试用循环去模拟所有的灯泡开关行为,能得到答案,但是大的case肯定会超时。
在讨论区找打了一个精妙的 解答 。任意一个数,如果可以分解成两个数a和b的乘积,那么当a和b不等的时候,就意味着成对出现了灯状态改变的行为。例如36=4x9,36=9x4,即偶数次的改变相当于不变。但是,如果a=b,在这种唯一的情况下,这种状态改变是成单的,并最终导致了灯泡状态的改变。也就是说,这种情况一定意味着该数可以被开平方。
public class Solution { public int bulbSwitch(int n) { return (int)Math.sqrt(n); } }
【题目】 Given a string array words
, find the maximum value of length(word[i]) * length(word[j])
where the two words do not share common letters. You may assume that each word will contain only lower case letters. If no such two words exist, return 0.
Example 1:
Given ["abcw", "baz", "foo", "bar", "xtfn", "abcdef"]
Return 16
The two words can be "abcw", "xtfn"
.
Example 2:
Given ["a", "ab", "abc", "d", "cd", "bcd", "abcd"]
Return 4
The two words can be "ab", "cd"
.
Example 3:
Given ["a", "aa", "aaa", "aaaa"]
Return 0
No such pair of words.
【解答】要找出互无重复字母的两个单词长度之积的最大值,很容易想到的是,不存在大小和递推关系,只有等和不等,因此如果没有特殊的方法,要两两比较,O(n²)这样的复杂度是少不了的。
那如果是这样的话,下面要解决的问题就是:从中给出两个单词,要比较这两个单词之间是否有字母重复,怎样让时间和空间复杂度尽量小。
开始尝试用HashSet来解决,即把一个单词中出现的字母全部找出来放到HashSet里面,如果另一个单词中的字母能从这个HashSet中找到,就说明有重复,反之则没有。我照着思路做了以后,超时了。
于是使用BitMap来改进:一个整数有32位,因此有32个位置可以表示0或者1,但是字母只有26个,因此如果某字母出现过,那么在相应位上面就为1而不为0,这样一个整数就可以表示任意一个单词中出现的字母种类。把单词转换成整数的方法可以作为“预处理”的步骤之一,而实际在实际的比较中,只需要先看两个单词所代表的整数相“与”,如果全0则表示无重复字母。用这种方法可比比较整个HashSet效率高多了。
public class Solution { public int maxProduct(String[] words) { // bit map List<Integer> list = new ArrayList<>(); for (String w : words) { int num = 0; for (int i=0; i<w.length(); i++) { num = num | getMask(w.charAt(i)); } list.add(num); } int max = 0; for (int i=0; i<list.size(); i++) { for (int j=i+1; j<list.size(); j++) { if ( ( list.get(i).intValue() & list.get(j).intValue() ) != 0 ) continue; max = Math.max(words[i].length() * words[j].length(), max); } } return max; } private int getMask(char c) { return 1 << (c - 'a'); } }
【题目】 Given a string which contains only lowercase letters, remove duplicate letters so that every letter appear once and only once. You must make sure your result is the smallest in lexicographical order among all possible results.
Example:
Given "bcabc"
Return "abc"
Given "cbacdcbc"
Return "acdb"
【解答】要求去重复字母,并且去重完毕之后,要求得到的字符串字典字母序最小。
下面这个是我一开始的解答,思路是:
举例来说:对于字符串“acbab”,构造这样的map:
然后开始从a-z遍历:
于是结果为“acb”,看起来逻辑是对的。
但是写完代码跑用例的时候傻眼了,有一个case用这个方法是过不去的,比如“cbacdcbc”,用上面的办法得到的是“adbc”,但实际应该是“acdb”。
原因在于,选a的时候没问题,选b的时候也没问题,但是选c的时候,上面的方法本质上是一种贪心的策略,因此挑了一个出现在b右侧的c,而在这里用贪心是行不通的,因为d的关系。
public class Solution { public String removeDuplicateLetters(String s) { Map<Character, List<Integer>> map = new HashMap<>(); for (int i=0; i<s.length(); i++) { char ch = s.charAt(i); if (!map.containsKey(ch)) map.put(ch, new ArrayList<>()); map.get(ch).add(i); } int lastPos = -1; Map<Character, Integer> posMap = new HashMap<>(); for (int i=0; i<26; i++) { char ch = (char)('a' + i); if (!map.containsKey(ch)) continue; if (lastPos==-1) { lastPos = map.get(ch).get(0); } else { List<Integer> list = map.get(ch); if (list.get(list.size()-1) > lastPos) { lastPos = binarySearch(map.get(ch), lastPos); } else { lastPos = map.get(ch).get(0); } } posMap.put(ch, lastPos); } List<Character> resultList = new LinkedList<>(); for (Map.Entry<Character, Integer> entry : posMap.entrySet()) { char ch = entry.getKey(); int pos = entry.getValue(); int insertPos = 0; while(insertPos<resultList.size() && pos>posMap.get(resultList.get(insertPos))) insertPos++; resultList.add(insertPos, ch); } String result = ""; for (char ch : resultList) { result += ch; } return result; } private int binarySearch(List<Integer> list, int pivot) { int left = 0; int right = list.size()-1; while (left<=right) { int mid = (left+right)/2; if (list.get(mid)>pivot) right = mid - 1; else left = mid + 1; } return list.get(left); } }
在题目的讨论中有一种正确而且简洁的 解法 。思路是:
这种方法也是贪心,但是每次贪心选出来的字母却不一定是从a到z这样的顺序的,原因在于:不是根据字典序,而是从左至右遍历s,每次因为某一个字母的所有位置都被找出来了,就跳出循环,但是选中的字却确不是当时遍历到的字母,而是当时状态下遍历过的的最小字母。该字母左边的全部子串都删掉,右边则把重复出现的该字母删掉。
还用上面的“cbacdcbc”举例:
public class Solution { public String removeDuplicateLetters(String s) { int[] cnt = new int[26]; int pos = 0; for (int i = 0; i < s.length(); i++) cnt[s.charAt(i) - 'a']++; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) < s.charAt(pos)) pos = i; if (--cnt[s.charAt(i) - 'a'] == 0) break; } return s.length() == 0 ? "" : s.charAt(pos) + removeDuplicateLetters(s.substring(pos + 1).replaceAll("" + s.charAt(pos), "")); } }
【题目】 You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i]
is the number of smaller elements to the right of nums[i]
.
Example:
Given nums = [5, 2, 6, 1] To the right of 5 there are 2 smaller elements (2 and 1). To the right of 2 there is only 1 smaller element (1). To the right of 6 there is 1 smaller element (1). To the right of 1 there is 0 smaller element.
Return the array [2, 1, 1, 0]
.
【解答】每次取nums数组的最右边的x个数,形成数列arr,把其中比arr[0]小的数的个数统计出来为y,随着x从n变到1,得出这个统计值y变化的结果并形成数组返回。
这个x如果是从1变到n,可能会更好计算。因为我每时每刻只要维护一个有序数组用来表示当前arr中所有的数们,只不过他们是被排序了的,x每次递增则意味着要从nums里面取一个新数放到这个有序数组里面去。根据放进去的位置,就能够知道当时有多少个数比它小。
public class Solution { public List<Integer> countSmaller(int[] nums) { List<Integer> sorted = new ArrayList<>(nums.length); List<Integer> result = new ArrayList<>(nums.length); for (int i=nums.length-1; i>=0; i--) { int index = insert(nums[i], sorted); result.add(0, index); } return result; } private int insert(int num, List<Integer> sorted) { if (sorted.isEmpty()) { sorted.add(num); return 0; } int left = 0; int right = sorted.size() - 1; while(left<=right) { int mid = (left+right)/2; if (sorted.get(mid)>=num) { right = mid-1; } else { left = mid+1; } } sorted.add(left, num); return left; } }
【题目】 Write a program to find the n th super ugly number.
Super ugly numbers are positive numbers whose all prime factors are in the given prime list primes
of size k
. For example, [1, 2, 4, 7, 8, 13, 14, 16, 19, 26, 28, 32]
is the sequence of the first 12 super ugly numbers given primes
= [2, 7, 13, 19]
of size 4.
Note:
(1) 1
is a super ugly number for any given primes
.
(2) The given numbers in primes
are in ascending order.
(3) 0 < k
≤ 100, 0 < n
≤ 10 6 , 0 < primes[i]
< 1000.
【解答】首先读懂题意。Super Ugly数是指一串正数,他们的质因数由给定的prime list组成,现在给定一个prime list,要求第k个Super Ugly数。
建立这个Super Ugly数序列的数组arr,并且首元素置为1。
再创建一个indexArr数组,其中的下标i表示在primes中的第i个质数,而存放的值表示arr中的下标。为什么要建立它,主要是因为对于任何一个在primes中的质数,如果要乘以arr中的某一个数的时候,需要用一个数记录当前在哪个数上面,而它前面的已经乘过了,因此现在要乘以primes[x]的话,需要从arr[indexArr[x]]开始。也就是说,这是一个存放的是下标的数组。这个思路其实有点绕,也是这个题目的关键,如果这一步想出来了这个题就没有难度了。
最后,注意去重的处理。
总的来说,我觉得这道题要做对挺难的啊,不止Medium难度。
public class Solution { public int nthSuperUglyNumber(int n, int[] primes) { if (n<=0 || primes==null || primes.length==0) throw new IllegalArgumentException(); int[] arr = new int[n]; arr[0] = 1; // each prime owns its dedicated index on arr int[] indexArr = new int[primes.length]; for (int i=1; i<n; i++) { int min = Integer.MAX_VALUE; int posToIncrease = -1; for (int j=0; j<primes.length; j++) { int index = indexArr[j]; int prime = primes[j]; if (prime*arr[index] < min) { min = prime * arr[index]; posToIncrease = j; } } indexArr[posToIncrease] += 1; // dedup if(arr[i-1] == min) i--; else arr[i] = min; } return arr[arr.length-1]; } }
【题目】 Given n
balloons, indexed from 0
to n-1
. Each balloon is painted with a number on it represented by array nums
. You are asked to burst all the balloons. If the you burst balloon i
you will get nums[left] * nums[i] * nums[right]
coins. Here left
and right
are adjacent indices of i
. After the burst, the left
and right
then becomes adjacent.
Find the maximum coins you can collect by bursting the balloons wisely.
Note:
(1) You may imagine nums[-1] = nums[n] = 1
. They are not real therefore you can not burst them.
(2) 0 ≤ n
≤ 500, 0 ≤ nums[i]
≤ 100
Example:
Given [3, 1, 5, 8]
Return 167
nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> [] coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
【解答】这道题其实难度我觉得Medium比较合适。
直观上,要使结果尽可能大,那么大的数要晚些引爆,这样不断参与乘法运算,就能使结果尽可能大。
对于首尾气球的处理,题目已经给了提示,nums[-1] = nums[n] = 1,因此创建一个长度为nums.length+2的数组。动态规划的记忆数组cache[from][to]表示从from个气球到to个气球,全部爆炸以后得到的值是多少。
接着,每次爆炸的时候,考虑根据切分点i把当前气球序列分成leftPart和rightPart两部分,其中leftPart为[from, i),rightPart为(i, to],因此递归求出leftPart和rightPart的值,并和nums[i]相乘,取不同i取值下的最大值。
public class Solution { public int maxCoins(int[] nums) { int[] clone = new int[nums.length + 2]; clone[0] = 1; clone[nums.length + 1] = 1; for (int i = 0; i < nums.length; i++) clone[i + 1] = nums[i]; int[][] cache = new int[nums.length + 2][nums.length + 2]; return maxCoins(clone, 1, nums.length, cache); } public int maxCoins(int[] nums, int from, int to, int[][] cache) { if (cache[from][to] > 0) return cache[from][to]; int max = 0; for (int i = from; i <= to; i++) { int leftPart = maxCoins(nums, from, i - 1, cache); int rightPart = maxCoins(nums, i + 1, to, cache); max = Math.max(max, leftPart + nums[from - 1] * nums[i] * nums[to + 1] + rightPart); } cache[from][to] = max; return max; } }