学了三天容器的知识了,追踪了不少次源码,整体上有所感悟,今天再背背容器的相关面试题啥的就结束了,明天开始学JVM。
暴力的方法直接就能想出来,两层for嘛,第一层遍历每个数,第二层再遍历一次,每次都看看和等不等于target。
如何用哈希的知识优化呢,哈希表查询时间O(1),所以我们可以减少一层for循环,遍历数组的时候,查表,看看表里有没有值等于target-nums[i]的,这就是我们需要的值。
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int k = target - nums[i];
if(map.containsKey(k)){
return new int[]{map.get(k), i};
}
map.put(nums[i], i);
}
return null;
}
}
我想的是,每次遍历这个字符串,每次遍历的这个字符都当作第一个节点,然后建立一个哈希表,一直添加元素直到有重复的,同时维护一个ans,记录最大值。
class Solution {
public int lengthOfLongestSubstring(String s) {
// 每次找都用一个新的hash表,用来存放每个元素,这样就能知道重复没。
//维护一个ans,每次取最大值。
int ans = -1;
for(int i = 0; i < s.length()-ans+1; i++){
HashMap map = new HashMap<>();
for(int j = i; j < s.length(); j++){
if(map.containsKey(s.charAt(j))){
break;
}
map.put(s.charAt(j), j);
}
ans = Math.max(ans, map.size());
}
return ans;
}
}
看了看别人的代码,可以从两方面优化一下。
思路如下:
用begin和end记录下标,比如abcabcbb,
begin一开始在a,end也在a,如果哈希表中没有这个元素:那么就加入到哈希表中,哈希表中存放的是元素和下标['a',0],如果有了:那么就把begin更新到这个元素的下一个位置,也就是从哈希表中查找到这个元素的value,value就是下标,加一后就是begin的位置。需要注意,如果是abba的情况,那么最后begin会更新到第一个a的后面,所以需要判断一下更新的begin是不是比当前的begin小。
public int lengthOfLongestSubstring(String s) {
int ans = 0, begin = 0;
HashMap map = new HashMap<>();
for(int end = 0; end < s.length(); end++){
char ch = s.charAt(end);
if(map.containsKey(ch)){
begin = Math.max(begin, map.get(ch) + 1);
map.put(ch, end);
}
else{
map.put(ch, end);
}
ans = Math.max(ans, end-begin+1);
}
return ans;
}
s
由英文字母、数字、符号和空格组成我们再优化一下,因为s的范围很小,ASCII码表示足矣,ASCII码只有128位,所以我们完全可以用数组来替换HashMap。s的每个元素对应的ASCII码就是数组的下标,数组存放的就是该字符在元素中出现的位置。
class Solution {
public int lengthOfLongestSubstring(String s) {
int ans = 0, begin = 0;
int[] map = new int[130];
for(int i = 0; i < 130; i++) map[i] = -1;
for(int end = 0; end < s.length(); end++){
char ch = s.charAt(end);
if(map[ch] != -1){//出现过
begin = Math.max(begin, map[ch] + 1);
map[ch] = end;
}
else{
map[ch] = end;
}
ans = Math.max(ans, end-begin+1);
}
return ans;
}
}
好久没做过二分的题了,今天做一道二分的题目练练手,这个题用二分的话思路还是挺简单的。
先给药水(potions)排个序,我们遍历每个咒语(spells),将每个咒语的值在药水(potions)里二分查找,找的是最左侧的元素,因为可能有重复值。
其实就是把二分法搜索最左侧的元素代码改动一下,判断条件是spell*arr[m]>=success就缩小,spell是当前咒语,arr[m]是当前的药水,如果强度足够了(超过success),那么就继续缩小右边界j。
最后返回药水长度-i的值,就是使用当前咒语搭配药水,强度足够的药水的个数。
注意success的类型是long,spell和arr注意也转成long,要不然通过不了。
class Solution {
public int[] successfulPairs(int[] spells, int[] potions, long success) {
Arrays.sort(potions);
int[] ans = new int[spells.length];
int cnt = 0;
for(int i = 0; i < spells.length; i++){
ans[cnt++] = Search(spells[i], potions, success);
}
return ans;
}
public int Search(int spell, int[] arr, long success){
int i = 0, j = arr.length-1;
while(i <= j){
int m = (i + j) >>> 1;
if(((long)spell*(long)arr[m]) >= success){//如果大了或者等于,一直缩小,找到最小的
j = m-1;
}
else{
i = m+1;
}
}
//此时i的值就是最左侧的元素下标了.
return arr.length-i;
}
}
TreeSet能排序是因为它可以接收一个比较器对象。
进入源码,我们发现TreeSet底层原理还是TreeMap,所以TreeSet源码超级短,就是一直调用TreeMap的方法而已。
追踪一下add方法,调用了put方法。
第一个值会走这个逻辑,compare(key, key),注释说了检查一下类型,有可能比较器是null
再添加一个值就要执行下面这个核心逻辑,cpr就是我们比较器对象,do-while循环查找插入的位置,cmp是当前的key和t的key比较的结果,用这个结果决定添加的位置。
Comparator super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
关键在于:
return t.setValue(value);
这句话说明当cmp比较的结果为0,那么就不添加,而是替换值,并且返回之前的值。所以treeset里判断是不是重复元素取决于我们重写的compare!
举个例子,如果把比较规则写成字符串长度相减,那么就只添加一个值,因为添加的长度都是3,compare返回的结果都是0
常用方法:
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("sss");
list.add("aa");
list.add("bbbb");
list.add("cccc");
list.add("dddddd");
// reverse(List) 反转
Collections.reverse(list);
System.out.println("reverse " + list);
// shuffle(List)打乱
Collections.shuffle(list);
System.out.println("shuffle " + list);
// sort(List)自然排序
Collections.sort(list);
System.out.println("sort " + list);
// sort(List, Comparator)
// Collections.sort(list, new Comparator() {
// @Override
// public int compare(Object o1, Object o2) {
// return ((String)o1).length() - ((String)o2).length();
// }
// });
Collections.sort(list, (o1, o2) -> ((String)o1).length() - ((String)o2).length());
System.out.println("sort2: " + list);
// swap下标交换
Collections.swap(list, 0, 2);
System.out.println("swap: " + list);
// max自然顺序的最大元素,也可以传入比较器;min同理,省略不写
Comparable max = Collections.max(list);
System.out.println("max自然顺序的最大元素 " + max);
Object max1 = Collections.max(list, (o1, o2) -> ((String) o1).length() - ((String) o2).length());
System.out.println("max长度最大元素 " + max1);
// frequency返回指定元素出现的次数
Collections.frequency(list, "aa");
System.out.println("frequency返回指定元素出现的次数 " + max1);
// copy(List desc, List src) 从src拷贝到desc中(浅拷贝),desc长度必须大于等于src才行
// 需要注意:desc = list并不是拷贝,而是地址指向同一个对象而已。
ArrayList desc = new ArrayList();
desc = list;
Collections.copy(desc, list);
System.out.println("copy: " + desc);
// replaceAll(List, OldVal, NewVal)旧值替换
Collections.replaceAll(list, "aa", "aaaaaaa");
System.out.println("replaceAll: " + list);
}
控制台输出:
reverse [dddddd, cccc, bbbb, aa, sss]
shuffle [aa, bbbb, cccc, sss, dddddd]
sort [aa, bbbb, cccc, dddddd, sss]
sort2: [aa, sss, bbbb, cccc, dddddd]
swap: [bbbb, sss, aa, cccc, dddddd]
max自然顺序的最大元素 sss
max长度最大元素 dddddd
frequency返回指定元素出现的次数 dddddd
copy: [bbbb, sss, aa, cccc, dddddd]
replaceAll: [bbbb, sss, aaaaaaa, cccc, dddddd]