参考了很多大佬的题解,仅作为自己学习笔记用。


数据结构相关

第一章 字符串

1.字符串循环移位包含

给定两个字符串 s1 和 s2,要求判定 s2 是否能够被 s1 做循环移位得到的字符串包含
解决办法:s1 进行循环移位的结果是 s1s1 的子字符串,因此只要判断 s2 是否是 s1s1 的子字符串即可。

public class Exer {public boolean strStr(String s1, String s2) {StringBuilder sb = new StringBuilder(s1);sb.append(s1);return sb.toString().contains(s2);}
}

思路:先进行拼串,看s2是不是s1s1的子字符

2.字符串循环移位

将字符串向右循环移动 k 位
解决办法:从k处分开,前后字符串翻转后拼接再翻转

public class Exer {public static void main(String[] args) {String s = "abcd123";int k = 3 ;String overturn = overturn(s, k);System.out.println(overturn);}//拼接截取public static String overturn(String s,int k) {StringBuilder sb = new StringBuilder(s);sb.append(s);return sb.toString().substring(sb.length()-k-s.length(),sb.length()-k);}//对前后两部分的字符串都进行翻转,拼串后再翻转public static String overturn(String s, int k) {String s2 = s.substring(s.length() - k, s.length());String s1 = s.substring(0, s.length() - k);StringBuilder s11 = new StringBuilder(s1).reverse();StringBuilder s22 = new StringBuilder(s2).reverse();return s11.append(s22).reverse().toString();}
}

思路:

  1. 首先将字符串分开截取成两段,前字符串和后字符串
  2. 分别进行翻转
  3. 反转后进行拼接即可得到

3.字符串中单词的翻转

将每个单词翻转,然后将整个字符串翻转

public class Exer {public static void main(String[] args) {String s = "I am a student";String s1 = word_overturn(s);System.out.println(s1);}public static String word_overturn(String s) {StringBuilder sb = new StringBuilder();String[] strs = s.trim().split(" ");for (int i = strs.length - 1; i >= 0; i--) {if(strs[i].equals(""))continue;sb.append(strs[i] + " ");}return sb.toString().trim();}
}

思路:可以用栈来实现,但这里用个简单的

  1. 将字符串去首尾的空格,按照空字符串分为单词
  2. 倒着将所有单词拼起来,如果是空字符跳过
  3. 最后返回值再去一下首尾

4.两个字符串包含的字符是否完全相同

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词

 public boolean isAnagram(String s, String t) {char[] schars = s.toCharArray();char[] tchars = t.toCharArray();Arrays.sort(schars);Arrays.sort(tchars);return Arrays.equals(schars,tchars);}//将s每个字符按照26个字母的数量存入,再用t减掉,如果有字母数量不是0的话就不是字母异位词public boolean isAnagram(String s,String t) {int[] arr = new int[26];for (char c : s.toCharArray()) {arr[c - 'a']++;}for (char c : t.toCharArray()) {arr[c - 'a']--;}for (int i : arr) {if (i != 0) {return false;}}return true;}

思路:

  1. 取得字符的char[]数组
  2. 字符底层都是数字,排序之后,只要两个数组不一样就说明单词组成不一样
  3. 调用数组的equals方法即可判断

5. 计算一组字符集合可以组成的回文字符串的最大长度

给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。

class Solution {public int longestPalindrome(String s) {int[] cnts = new int[256];for (char c : s.toCharArray()) {cnts[c]++;}int palindrome = 0;for (int cnt : cnts) {palindrome += (cnt / 2) * 2;}if (palindrome < s.length()) {palindrome++;}return palindrome;}
}

思路:

  1. 发现回文数最大长度与奇偶数有关,所以要统计每个字符出现的次数
  2. 使用长度为 256 的整型数组来统计每个字符出现的个数,每个字符有偶数个可以用来构成回文字符串。cnt / 2可以把出现为1次的筛掉
  3. 如果回文串的长度小于整个字符串的长度,那必有一个出现一次的字符可以放在回文串的正中间

6.同构字符串

给定两个字符串 s 和 t,判断它们是否是同构的。
如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。

class Solution {public boolean isIsomorphic(String s, String t) {if(s.length() != t.length())return false;for (int i = 0; i < s.length(); i++) {if(s.indexOf(s.charAt(i)) != t.indexOf(t.charAt(i))){return false;}}return true;}
}

思路:

  1. 首先,同构同构,长度不一样绝对不同构
  2. 记录每个字符上一次出现的位置,两字符串的当前字符上次位置不同的话就是不同构

7.回文子串

给你一个字符串s,请你统计并返回这个字符串中回文子串的数目。

class Solution {int count = 0;public int countSubstrings(String s){if(s == null || s.length() < 1){return 0;}for (int i = 0; i < s.length(); i++) {extendPalindrome(s,i,i+1);extendPalindrome(s,i,i);}return count;}public void extendPalindrome(String s, int left, int right){while(left >= 0 && right <s.length() && s.charAt(left--) == s.charAt(right++)){count++;}}
}

思路:中心扩散法

  1. 把每一个字符看作中心向两边扩散,扩散的子串判断是否为回文串
  2. 字符串的构成可能是奇数也可能是偶数,所以要考虑两种情况作为中心
  3. 定义左右指针从中心向两边扩散,判断回文子串,是就记录,最后得到结果count

8.判断一个整数是否是回文数

如:121,则返回true
方法一:把数换成字符,遍历首尾字符比较

class Solution {public boolean isPalindrome(int x) {if(x < 0)return false;String s = String.valueOf(x);for (int i = 0,j = s.length() - 1; i < s.length() && i < j; i++,j--) {if (s.charAt(i) != s.charAt(j)) {return false;}}return true;}
}

方法二: 直接计算倒序数,从后向前计算每一位数,计算完成后和原数对比

     if(x >= 0){if(x == 0){return true;}int cur = 0;int num = x;while(num != 0){cur = cur * 10 + num % 10;//当前数乘10再把num的个位数加上num /= 10;//此时num再小10倍}return cur == x;}return false;

结果:

9.统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数

暴力手撕成功,但超时

public static int countBinarySubstrings(String s) {int time = 0;for (int i = 0; i < s.length(); i++) {for (int j = i+1; j < s.length(); j++) {String substring = s.substring(i, j + 1);if(isBSstr(substring)){time++;}}}return time;}public static boolean isBSstr(String s){if(s.length() % 2 != 0){return false;}if(s.contains("0") && !s.contains("1") || !s.contains("0") && s.contains("1")){return false;}if(s.substring(0,s.length()/2).contains("0") && s.substring(0,s.length()/2).contains("1")|| s.substring(s.length()/2,s.length()).contains("0") && s.substring(s.length()/2,s.length()).contains("1")){return false;}return true;}}

思路:

  1. 统计所有字串
  2. 对所有字串判断,从中间分开。①如果前半部分或后半部分有0且有1就false ②奇数个字串也false ③全为0或者全为1就false
  3. 最后统计得到结果
    改进:
class Solution {public int countBinarySubstrings(String s) {int preLen = 0, curLen = 1, count = 0;for (int i = 1; i < s.length(); i++) {if (s.charAt(i) == s.charAt(i - 1)) {curLen++;} else {preLen = curLen;curLen = 1;}if (preLen >= curLen) {count++;}}return count;}
}

思路:

  1. 该问题对字符串需进行逐位遍历,如果当前两字符相等,则设置一个当前相等的长度数curlen,每相等一次长度就加长一次
  2. 如果统计到当前两字符不相等,就把之前的当前长度数curlen赋值给之前的长度数prelen当前的长度数重新置为1
  3. 此时出现不一样的字符了(之前是0),对于此时不一样的字符(1)后面的字符如果一样(1),比如00011,则还需要统计0011
  4. 所以每次移位复制结束后,再判断一下如果当前长度数(curlen) <= 之前长度数(prelen),则需要累加统计数,即可得到结果

10.(难)亲密字符串

给你两个字符串 s 和 goal ,只要我们可以通过交换 s 中的两个字母得到与 goal 相等的结果,就返回 true ;否则返回 false 。

class Solution {public boolean buddyStrings(String s, String goal) {if(s.length() != goal.length())return false;if (s.equals(goal)) {int[] sarr = new int[26];for (char c : s.toCharArray()) {if (++sarr[c - 'a'] > 1) {return true;}}return false;}char sc = ' ';char gc = ' ';int time = 0,index = 0;for (int i = 0; i < s.length(); i++) {if (s.charAt(i) != goal.charAt(i)) {if (time == 1) {index = i;time++;continue;} else if (time == 2) {return false;}sc = s.charAt(i);gc = goal.charAt(i);time++;}}return !(sc != goal.charAt(index) || gc != s.charAt(index));}
}

思路:

  1. 如果两字符串相等,只要有两个重复的字符就可以交换,如果有大于1的,就一定有重复字母
  2. 长度不等返回false,定义第二次不相同时位置的索引index,第一次不同时的字符sc,gc,time是字符不同的次数
  3. 对字符开始遍历,当前两字符串中的字符相等直接进入下一循环。
  4. 如果不相等,记录此时不相等的次数,第二次不相等就记录下当前索引位置,第二次不相等就只能false了因为题目只让交换一次。记录第一次不相等时两字符串中的字符
  5. 最后比较第一次与第二次字符是否对应相等,是否完成了交换,返回结果

字符串题型总结

  1. 双指针法是字符串处理的常客,KMP算法是字符串查找最重要的算法。
  2. 一般字符串题型比较简单,会应用一些字符串常用的API来解决
  3. equals():比较两个字符串是否相等
  4. equalsIgnoreCase( ):忽略大小写的两个字符串是否相等比较
  5. String.valueOf():把数字转换成String类型(不用担心object是否为null值这一问题)
  6. subString():截取字符串中的一段字符串
    String str;
    (1)str=str.substring(int beginIndex);
    截取掉str从首字母起长度为beginIndex的字符串,将剩余字符串赋值给str;
    (2)str=str.substring(int beginIndex,int endIndex);
    截取str中从beginIndex开始至endIndex结束时的字符串,并将其赋值给str;
  7. charAt():返回指定索引处char值
  8. toLowerCase():将所有在此字符串中的字符转化为小写 toUpperCase()方法: 将字符全部转化为大写
  9. indexOf():指出 String 对象内子字符串的开始位置
  10. replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换
  11. getBytes():得到一个系统默认的编码格式的字节数组
  12. StringBuffer的append()方法:用于拼串
  13. StringBuffer或者StringBuilder.toString()就可以将其转化为String类型
  14. 获取字符串长度方法length():str.length();
  15. 字符串比较 :
    (1) 不忽略字符串大小写情况下字符串的大小比较方法compareTo(another str)
    (2) 忽略字符串大小写情况下字符串的大小比较方法compareTOIgnoreCase(another str)
    以上都是输出三种比较结果:若该字符串的Unicode值<参数字符串的Unicode值,结果返回一负整数;若若该字符串的Unicode值=参数字符串的Unicode值,结果返回0;若该字符串的Unicode值>参数字符串的Unicode值,结果返回一正整数。
    (3) 不忽略字符串大小写情况下判别字符串相等的方法equals(another str)
    当且仅当str1和str2的长度相等,且对应位置字符的Unicode编码完全相等,返回true,否则返回false
    (4) 忽略字符串大小写情况下判别字符串相等的方法equalsIgnoreCase(another str)
  16. str.toCharArray();将字符串改成char型数组

主要是以一些API的应用为主,逻辑方面不会太难

第二章 栈和队列

1.用栈实现队列

class MyQueue{Stack<Integer> stack1 = new Stack<>();Stack<Integer> stack2 = new Stack<>();public MyQueue() {}public void push(int x) {if(stack2.isEmpty()){stack1.push(x);}else {while (!stack2.isEmpty()){stack1.push(stack2.pop());}stack1.push(x);}}public int pop() {if(stack1.isEmpty() && stack2.isEmpty()){return 0;}while (!stack1.isEmpty()) {stack2.push(stack1.pop());}return stack2.pop();}public int peek() {if(stack1.isEmpty() && stack2.isEmpty()){return 0;}while (!stack1.isEmpty()) {stack2.push(stack1.pop());}return stack2.peek();}public boolean empty() {return stack1.isEmpty() && stack2.isEmpty();}
}

思路:

  1. 需要构造两个栈,由于栈的存储特点是先进后出正好与队列的先进先出相反,所以需要两个栈倒腾一下
  2. 添加的时候,通常要保持栈2空着。因为栈2是倒过来的,你不把栈2先倒腾过来就直接加到栈1回到是顺序混乱。所以添加的时候,只要栈2不空,就全部又添加回栈1,添加回来后再push新数
  3. 弹栈的时候,通常要保持栈1空着,因为栈2此时已经是按队列排好的数,所以此时栈1有数据要全部倒腾到栈2,直接用栈2的pop即可达到队列pop的目的
  4. 取队列的头指针(即最先进的数),直接将弹栈的方法复制过来,返回值用栈2的peek即可
  5. 两个栈都空说明队列为空

2.用队列实现栈

class MyStack {private Queue<Integer> queue;public MyStack() {queue = new LinkedList<Integer>();}public void push(int x) {queue.add(x);int len = queue.size();while (len-- >1){queue.add(queue.poll());}}public int pop() {return queue.remove();}public int top() {return queue.peek();}public boolean empty() {return queue.isEmpty();}
}

思路:

  1. 关键在push方法上,由于队列的添加方法正好和栈的添加方式相反,所以把添加好的队列倒过来即可,由于队列采用了LinkedList所以可以从两头添加,比起队列用栈来实现简单
  2. 添加的方法,对于当前的队列,获取它的长度,用队列的方法,把队尾的数据添加到队头,每添加一次,长度要减减,否则退不出循环,循环结束,队列就被倒过来了
  3. 剩下的方法直接用队列的即可

3. 最小值栈

要求定义的结构模拟栈结构,还能得到当前栈的最小值。
可以直接调用栈来实现,但是最好模拟一个类似栈的结构

class MinStack {private  Node head;public MinStack(){}public void push(int val) {if(head == null){head = new Node(val,val);}else {head = new Node(val,Math.min(head.min,val),head);}}public void pop() {head = head.next; }public int top() {return head.val;}public int getMin() {return head.min;}private class Node{int val;int min;Node next;private Node(int val,int min){this(val,min,null);}private Node(int val,int min,Node next){this.val = val;this.next = next;this.min = min;}}
}

思路:

  1. 模拟链表,将节点添加在队首,设置一种节点的构造器用于记录最小值
  2. 将每次添加的数的节点放在队首,并且每次比较记录最小值
  3. 最后栈顶即为队首

4.有效的括号

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

class Solution {public boolean isValid(String s) {Stack<Character> stack = new Stack<>();if(s.length() % 2 == 0){for (int i = 0; i < s.length(); i++) {if(s.charAt(i) == '(' || s.charAt(i) == '[' || s.charAt(i) == '{'){stack.push(s.charAt(i));}else {if(!stack.isEmpty()) {if (!stack.isEmpty()) {if (stack.peek() == '(' && s.charAt(i) == ')') {stack.pop();} else if (stack.peek() == '[' && s.charAt(i) == ']') {stack.pop();} else if (stack.peek() == '{' && s.charAt(i) == '}') {stack.pop();} else {stack.push(s.charAt(i));}}else {return false;}}}if(stack.isEmpty()){return true;}else {return false;}}return false;}
}

思路:

  1. 用栈来解决
  2. 对于长度为奇数的字符串一定不符合规则。遍历每一个符号,如果是左括号直接压入栈;如果是右括号且栈为空的话,说明前面符号配对好了,现在右括号打头阵肯定不对。
  3. 如果是右括号且栈不为空的话,分情况讨论,如果当前是右括号,且栈顶是其对应的左括号,就说明配对了,把栈顶的左括号弹出即可。没有配对的右括号直接压入栈
  4. 最后遍历结束,如果栈不为空,说明还有右括号没配对所以false,即可得到结果

5.数组中元素与下一个比它大的元素之间的距离(每日温度) – 单调栈

请根据每日气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。
暴力解法:

class Solution {public int[] dailyTemperatures(int[] temperatures) {int[] arr = new int[temperatures.length];for (int i = 0; i < temperatures.length; i++) {for (int j = i + 1; j < temperatures.length; j++) {if(temperatures[i] < temperatures[j]){arr[i] = j - i;break;}}}return arr;}
}

单调栈解法:通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。 单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素的元素,优点是只需要遍历一次。
①单调栈之正序遍历:

     int[] arr = new int[temperatures.length];Arrays.fill(arr,0);Stack<Integer> stack = new Stack<>();for (int i = 0; i < temperatures.length; i++) {while(!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]){int previndex = stack.pop();arr[previndex] = i - previndex;}stack.push(i);}return arr;

结果:

②单调栈之逆序遍历;

     int[] arr = new int[temperatures.length];Arrays.fill(arr,0);Stack<Integer> stack = new Stack<>();for (int i = temperatures.length - 1; i >= 0; i--) {while(!stack.isEmpty() && temperatures[i] >= temperatures[stack.peek()]){stack.pop();}arr[i] = stack.isEmpty() ? 0 : stack.peek() - i;stack.push(i);}return arr;

结果:

正序和逆序的效率不同!

6.循环数组中比当前元素大的下一个元素 – 单调栈

给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。

class Solution {public int[] nextGreaterElements(int[] nums) {Stack<Integer> stack = new Stack<>();int[] res = new int[nums.length];Arrays.fill(res,-1);for (int i = 0; i < nums.length * 2; i++) {while (!stack.isEmpty() && nums[i % nums.length] > nums[stack.peek()]){res[stack.pop()] = nums[i % nums.length];}stack.push(i % nums.length);}return res;}
}

思路:

  1. 首先需要一个数组来装结果,并初始化-1
  2. 由于是循环数组,所以遍历原数组的2倍次,只有在栈不为空且当前元素大于栈顶元素的时候,就把当前元素的值给栈顶元素的结果,这样就表明之前元素的下一个更大元素已经有了结果
  3. 不满足上述情况的时候后,就把当前元素压入栈即可,等待比较
  4. 注意:如何形成循环数组,比较普遍而简单的方式是取模运算,将当前元素取模数组长度,就可以把下标 i 映射到数组长度的0 - N内。

栈和队列总结

  1. 栈和队列数据结构的特点是:
    ①栈特点就是一个先进后出的结构。
    ②队列特点就是一个先进先出的结构。
  2. 栈和队列的区别是:
    ①数据结构不同队列先进先出,栈先进后出。
    ②对插入和删除操作的"限定"。 栈是限定只能在表的一端进行插入和删除操作的线性表。 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。
    ③遍历数据速度不同。栈只能从头部取数据 也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性队列怎不同,他基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间。
  3. 对于题目中的数据需要得到其逆序的数据,或者说想改变后面添加的数据而不改变之前的数据的这种题型,可以考虑用栈
  4. 对于题目中的数据或者是对象需要一个一个取,或者获得其顺序需要遵循先入先出的原则,就可以采用队列,甚至由双端队列(Deque<Integer> que = new LinkedList<>();)实现
  5. 括号匹配是使用栈解决的经典问题。
  6. 单调栈的使用情景:通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈

第三章 链表

1.找出两个链表的交点

速解:各自前移,谁先空走谁的路,最后返回谁都可以
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

public class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {ListNode l1 = headA,l2 = headB;while (l1 != l2){l1 = (l1 == null) ? headB : l1.next;l2 = (l2 == null) ? headA : l2.next;}return l2;}
}

思路:

  1. 遍历两个链表,谁先遍历完指针就跳到对方那个链表继续遍历,如果有相交,那么相交之后的链表相同,如图:
    指针1遍历顺序:a1>a2>c1>c2>c3>b1>b2>b3>c1>c2>c3
    指针2遍历顺序:b1>b2>b3>c1>c2>c3>a1>a2>c1>c2>c3
  2. 如上例,如果有相交的部分,此时的相交的部分是相同的,所以返回谁都可以

2.反转链表

①头插法:

class Solution {public ListNode reverseList(ListNode head) {ListNode pre = null,cur = head;while (cur != null){ListNode nxt = cur.next;cur.next = pre;pre = cur;cur = nxt;}return pre;}
}

思路:

  1. 设置前中后三个指针,中指的是当前指针
  2. 进行交换,取出当前指针的下一个节点作后指针,把前指针赋给后指针,把中指针给前指针,最后把后指针给中指针,完成交换

②递归法:

class Solution {public ListNode reverseList(ListNode head) {if(head == null || head.next == null){return head;}ListNode listNode = reverseList(head.next);head.next.next = head;head.next = null;return listNode;}
}

思路:



上述两图确保了链表的指向反转,每一层递归的节点是他本身

3. 归并两个有序的链表 – 递归

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

class Solution {public ListNode mergeTwoLists(ListNode l1, ListNode l2) {if(l1 == null){return l2;}if(l2 == null){return l1;}if(l1.val < l2.val){l1.next = mergeTwoLists(l2,l1.next);return l1;}else {l2.next = mergeTwoLists(l1,l2.next);return l2;}}
}

思路:递归

  1. 递归依然要写终止条件,所以某个链表被合并完之后,返回对方那个链表即可
  2. 递归的理解:对于使用递归函数得到的变量,其实可以看做后面的数据已经被处理好了,直接看作是处理最外层的一次结果,这样递归就好理解了,所以当 l1.val > l2.val 时,说明 l1 此时要添加到 l2 后面,所以写成代码就表示为l2.next = mergeTwoLists(l1,l2.next);表示 l2 的后面 = l1 和 l2 后面剩下的节点的拼接,而此时由于递归,等式右边的已经是处理好的升序链表,就差这一步了,所以完成之后返回 l2 即可

4. 从有序链表中删除重复节点

①递归法:
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 。

class Solution {public ListNode deleteDuplicates(ListNode head) {if(head == null || head.next == null){return head;}head.next = deleteDuplicates(head.next);return head.val == head.next.val ? head.next : head;}
}

思路:递归

  1. 处理的是头节点之后的链表,所以头节点之后递归即可
  2. 终止条件指的是最后的节点,如果到了最后的节点就将它返回,回溯处理
  3. 返回的节点是根据当前节点和下一节点的值比较得出的,相同值就返回下一个节点,不同值就返回第一个节点

②单指针法:

class Solution {public ListNode deleteDuplicates(ListNode head) {if(head == null || head.next == null){return head;}ListNode cur = head;while (cur.next != null){if(cur.val == cur.next.val){cur.next = cur.next.next;}else {cur = cur.next;}}return head;}
}

思路:

  1. 首先进行数据校验
  2. 假设当前节点是头节点,开始寻找和当前节点相同值的节点,即只要当前节点的下一节点不为空,就一直向后找如果俩相同了,就把当前节点的下一节点改成当前节点的下下个节点,如果不相等,当前节点后移一下就可以了

③双指针法:

class Solution {public ListNode deleteDuplicates(ListNode head) {if(head == null || head.next == null){return head;}ListNode cur = head;ListNode nxt = null;while (cur != null){nxt = cur.next;while (nxt != null){if(cur.val == nxt.val){cur.next = nxt.next;}nxt = nxt.next;}cur = cur.next;}return head;}
}

思路:

  1. 通过双指针来寻找和删除相同的节点,同样先进行数据校验,确定当前节点和下一节点作为双指针
  2. 当前指针不为空时,确定下一节点,下一节点不为空时,判断双指针所指的节点,如果相等就按单指针的方法写代码,如果不等就把后指针(下一节点)向后移,移完了,就可以把当前节点往后移一位,再判断当前节点(前指针)和下一节点(后指针)

单双指针比较:
由于链表是升序链表,所以相同的值都是在一起的,只要后一个值和当前值不同,其实就可以停止遍历了,所以单指针法就是遍历的时候及时止损,而双指针法就是双层遍历,当前值和下一节点值不同时,之后的值也不会相同了,但还是在查找,浪费时间

5.删除链表的倒数第 N 个结点

速解:虚拟头节点、快慢指针
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {ListNode newHead = new ListNode(0);newHead.next = head;ListNode fast = newHead;ListNode slow = newHead;for (int i = 0; i < n; i++) {fast = fast.next;}while (fast != null && fast.next != null){fast = fast.next;slow = slow.next;}slow.next = slow.next.next;return newHead.next;}
}

思路:

  1. 首先介绍虚拟头节点:有时在删除时,有可能删除头节点,此时处理不好就会报错空指针,此时添加一个虚拟的头节点以防止出现这种异常。
  2. 定义一快一慢指针,快指针用来定位要删除的节点,慢指针用来删除节点,先把快指针移动n个单位,再同时移动快慢指针,快指针到末尾时,慢指针所指的下一个节点就是要删除的节点,进行删除,最后返回虚拟节点的下一个节点,注:不能返回head,因为保不齐删的就是head

6. 两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。必须进行实际结点的交换
①递归法:

class Solution {public ListNode swapPairs(ListNode head) {if(head == null || head.next == null){return head;}ListNode subResult = swapPairs(head.next.next);ListNode headnext = head.next;headnext.next = head;head.next = subResult;return headnext;}
}

思路:

  1. 根据示例,当只有头节点或者头节点为空的话都是它本身,故写出终止条件
  2. 把头节点下下个节点开始的看作处理好的节点,所以递归从下下个节点开始
  3. 现在要取得三个节点,分别是头节点、次头节点、处理完成的节点
  4. 最后一步把头结点的地址赋给次头节点的next,把处理完成的节点赋给次头节点的next,最后次头节点成了整个链表的头结点,要取得反转后的链表就得返回次头节点才能获得
    ②迭代法:
class Solution {public ListNode swapPairs(ListNode head) {ListNode dummyhead = new ListNode(0);dummyhead.next = head;ListNode prev = dummyhead;while (prev.next != null && prev.next.next != null){ListNode node1 = prev.next;ListNode node2 = node1.next;ListNode subhead = node2.next;node2.next = node1;node1.next = subhead;prev.next = node2;prev = node1;}return dummyhead.next;}
}

思路:

  1. 迭代法需要画图,首先确定4个点,在不进行头节点判断时,就需要引入虚拟头节点dummyhead
  2. 此时将dummyhead看作prev(首),node1,node2,subhead(子头节点、尾)
  3. 按照箭头写代码就可以调整链表的顺序
  4. 由于此次调整位置结束,prev = node1;表示更换下一个首节点
  5. 因为头节点不确定是否存在或者说有意义,此时要返回dummyhead.next;

7. 两数相加 II

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {Stack<Integer> stack1 = new Stack<>();Stack<Integer> stack2 = new Stack<>();while(l1 != null || l2 != null){if(l1 != null){stack1.push(l1.val);l1 = l1.next;}if(l2 != null){stack2.push(l2.val);l2 = l2.next;}}int num1 = 0;int num2 = 0;int carry = 0;ListNode result = null;while (!stack1.isEmpty() || !stack2.isEmpty() || carry == 1){num1 = stack1.isEmpty() ? 0 : stack1.pop();num2 = stack2.isEmpty() ? 0 : stack2.pop();ListNode listNode = new ListNode(0);if(num1 + num2 + carry > 9){listNode.val = num1 + num2 + carry - 10;carry = 1;}else {listNode.val = num1 + num2 + carry;carry = 0;}listNode.next = result;result = listNode;}return result;}
}

思路:

  1. 首先分析发现,链表最后一个是低位,如果从低位往高位加,链表很难倒着寻找,所以此时用栈
  2. 把每个链表头节点依次装入栈中,此时的栈顶就是最低位
  3. 代码第一步:将两个链表循环遍历装入栈
  4. 用两个数分别代表两个栈顶,并确定一个进位数,声明一个节点作为结果录入
  5. 当栈不空或者进位是1时,要继续添加节点。如果栈不空取他们栈顶元素数值,确定一个当前节点
  6. 判断如果有进位,就把栈顶元素和进位加起来减掉10,就是当前节点的值;如果没有进位栈顶元素加进位即可
  7. 最后就是传递最高位,即result。把当前最低位的节点的下一个节点设置成result初始值,接着把当前节点赋给结果节点,这样结果节点,慢慢就被推成了最高位
  8. 返回result即可得到结果

8. 回文链表

速解:切成两半,把后半段反转,然后比较两半是否相等
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
①切成两半,把后半段反转,然后比较两半是否相等:

class Solution {public boolean isPalindrome(ListNode head) {int length = getLength(head);ListNode cur = head;for (int i = 1; i < length / 2; i++) {cur = cur.next;}ListNode subhead = cur.next;cur.next = null;ListNode subcur = subhead;ListNode pre = null;while (subcur != null){ListNode nxt = subcur.next;subcur.next = pre;pre = subcur;subcur = nxt;}return isequal(head,pre);}public int getLength(ListNode head){int len = 0;while (head != null){++len;head = head.next;}return len;}public boolean isequal(ListNode head,ListNode subhead){while (head != null && subhead != null){if(head.val != subhead.val){return false;}head = head.next;subhead = subhead.next;}return true;}
}

思路:

  1. 计算链表长度,切割成两半,分成后面的链表,和前面的链表,对后面的链表进行反转
  2. 比较两链表的内容,即挨个节点进行比较,返回比较值

②把链表的值输出成数组,判断数组是不是回文的:

class Solution {public boolean isPalindrome(ListNode head) {int[] arr = new int[getLength(head)];int x = 0;while (head != null){arr[x++] = head.val;head = head.next;}int[] narr = new int[arr.length];for (int i = 0; i < arr.length; i++) {narr[i] = arr[arr.length - i - 1];}return Arrays.equals(narr,arr);}public int getLength(ListNode head){int len = 0;while (head != null){++len;head = head.next;}return len;}
}

思路:

  1. 把链表的值全部装到数组中
  2. 重新定义一个数组,把之前的倒过来装进这个数组
  3. 直接用数组的比较即可

9.分隔链表

速解:模拟,就分为K个数组,将对应的链表放入
给你一个头结点为 head 的单链表和一个整数 k ,请你设计一个算法将链表分隔为 k 个连续的部分。

每部分的长度应该尽可能的相等:任意两部分的长度差距不能超过 1 。这可能会导致有些部分为 null 。

这 k 个部分应该按照在链表中出现的顺序排列,并且排在前面的部分的长度应该大于或等于排在后面的长度。

返回一个由上述 k 部分组成的数组。

class Solution {public ListNode[] splitListToParts(ListNode head, int k) {int len = 0;ListNode temp = head;while (temp != null){len++;temp = temp.next;}int quotient = len / k,reminder = len % k;ListNode[] listparts = new ListNode[k];ListNode curr = head;for (int i = 0; i < k && curr != null; i++) {listparts[i] = curr;int partsize = quotient + (i < reminder ? 1 : 0);for (int j = 1; j < partsize; j++) {curr = curr.next;}ListNode next = curr.next;curr.next = null;curr = next;}return listparts;}
}

思路:拆分链表

  1. 不要动头节点,统计链表长度
  2. 计算分组数,用长度除k表示分具体组数,余数用于之后表示长的链表组
  3. 此时取头节点按照大组数分,开始遍历装节点,此时也考虑了k比len长的情况,只要k长,当前节点就不要遍历,之后自动就取空了
  4. 将当前节点先装到当前组的当前位置,再计算当前组的长度,然后把当前节点移动到当前组的末尾
  5. 保存当前节点下一个节点,作为下一组的第一个节点。在把当前节点的下一个节点置空,说明当前组已经和下一组断开。最后当前节点改为刚才的下一组的第一个节点
  6. 返回每一组即可

10.链表元素按奇偶聚集

速解:找出奇偶头,各自连接对应的节点,最后两表链接
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

class Solution {public ListNode oddEvenList(ListNode head) {if(head == null || head.next == null){return head;}ListNode odd = head;ListNode even = head.next;ListNode odd_head = odd;ListNode even_head = even;while (even != null && even.next != null) {odd.next = even.next;odd = even.next;even.next = odd.next;even = odd.next;}odd.next = even_head;return odd_head;}
}

思路:

  1. 考虑特殊情况,头节点只有一个或者空时,直接返回本身即可
  2. 记录当前奇节点,头奇节点,当前偶节点,头偶节点
  3. 当偶节点不为空且偶节点的下一个不为空时,说明需要继续遍历,偶节点的下一节点赋给奇节点的下一节点,奇节点此时跳到偶节点的下一节点,奇节点同理
  4. 最后把奇节点的头接到偶节点的下一节点即可,最后返回偶节点头

链表总结

解题技巧一:虚拟头节点

  1. 链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了头结点的尴尬,因为头结点没有前一个节点了。每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题。给链表添加一个虚拟头结点为新的头结点,return 头结点是 return dummyNode->next;, 这才是新的头结点。
/*** 添加虚节点方式* 时间复杂度 O(n)* 空间复杂度 O(1)* @param head* @param val* @return*/
public ListNode removeElements(ListNode head, int val) {if (head == null) {return head;}// 因为删除可能涉及到头节点,所以设置dummy节点,统一操作ListNode dummy = new ListNode(-1, head);ListNode pre = dummy;ListNode cur = head;while (cur != null) {if (cur.val == val) {pre.next = cur.next;} else {pre = cur;}cur = cur.next;}return dummy.next;
}
  1. 无法高效获取长度,无法根据偏移快速访问元素,是链表的两个劣势。然而面试的时候经常碰见诸如获取倒数第k个元素,获取中间位置的元素,判断链表是否存在环,判断环的长度等和长度与位置有关的问题。都可以结合双指针来考虑解决
    链表常见问题
  2. 链表解题的最好方法是画图,否则容易搞混,一般用画图解决

第四章 数组与矩阵

1.把数组中的 0 移到末尾

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
①用栈:

class Solution {public void moveZeroes(int[] nums) {Stack<Integer> num = new Stack<>();int zerotime = 0;for (int i = 0; i < nums.length; i++) {if(nums[i] == 0){zerotime++;}else {num.push(nums[i]);}}for (int i = nums.length - 1; i >= 0 ; i--) {if(zerotime != 0){nums[i] = 0;zerotime--;}else if(!num.isEmpty()) {nums[i] = num.pop();}}}
}

思路:

  1. 分别创造数字栈和零计数,正序遍历将数组的数装进栈
  2. 倒序遍历数组,先放入0,再放入常数
    效果不理想

②一次遍历:

class Solution {public void moveZeroes(int[] nums) {if(nums == null || nums.length == 1){return;}int j = 0;for (int i = 0; i < nums.length; i++) {if(nums[i] != 0){int temp = nums[i];nums[i] = nums[j];nums[j++] = temp;}}}
}

思路:

  1. 特殊情况要讨论
  2. 确定两个指针,当前位置不为0时,记录此时位置的数
  3. 交换 j 位置的数,然后 j 后移

2.改变矩阵维度

给定矩阵,改变它的指定维度

class Solution {public int[][] matrixReshape(int[][] mat, int r, int c) {int m = mat.length;int n = mat[0].length;if(m * n != r * c){return mat;}int[][] ans = new int[r][c];for (int i = 0; i < m * n ; ++i) {ans[i / c][i % c] = mat[i / n][i % n];}return ans;}
}

思路:

  1. 取出原矩阵的行与列,如果原矩阵与指定矩阵的行与列不同说明没法进行转换,不能进行重塑
  2. 根据指定行与列构建新的矩阵
  3. 重要知识点:映射: 即为: (i,j)→i×n+j 同样地,我们可以将整数 x 映射回其在矩阵中的下标,即
    i=x / n
    j=x % n 其中 / 表示整数除法,% 表示取模运算。
    那么题目需要我们做的事情相当于:将二维数组nums 映射成一个一维数组;将这个一维数组映射回 r 行 c 列的二维数组。
  4. 对于 x ∈ [0, mn)x∈[0,mn),第 x 个元素在 nums 中对应的下标为 (x / n,x % n),而在新的重塑矩阵中对应的下标为 (x / c,x % c),我们直接进行赋值即可。

3. 找出数组中最长的连续 1

二进制数组,只有 0 或 1

class Solution {public int findMaxConsecutiveOnes(int[] nums) {if(nums == null)return 0;int time = 0;int max = 0;for (int i = 0; i < nums.length; i++) {if(nums[i] != 1){max = Math.max(max,time);time = 0;}else {time++;}}return Math.max(max,time);}
}

思路:

  1. 特殊情况要讨论,否则会报空指针
  2. 构造一个记录当前连续数和最大连续数,遍历寻找
  3. 遇到 0 就判断一下最大连续数,然后当前连续数置为0,继续寻找
  4. 最后返回最大连续数即可

4.有序矩阵查找

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。
①暴力法:太简单达不到高效。双层遍历查找。
②从左下右上开始查找:

class Solution {public boolean searchMatrix(int[][] matrix, int target) {int cur_m = 0,cur_n = matrix[0].length - 1;while (cur_m < matrix.length && cur_n >= 0){if(matrix[cur_m][cur_n] < target){cur_m++;}else if(matrix[cur_m][cur_n] > target){cur_n--;}else {return true;}}return false;}
}

思路:

  1. 从左下或者右上查找,可以根据与target的值大小分情况查找。比如从右上查找,比target大的值向下找,比target小的值往左找
  2. 找到直接返回true,找不到返回false

?5.有序矩阵的 Kth Element

给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。

class Solution {public int kthSmallest(int[][] matrix, int k) {int n = matrix.length;int left = matrix[0][0];int right = matrix[n - 1][n - 1];while (left < right){int mid = left + ((right - left) >> 1);if(check(matrix,mid,k,n)){right = mid;}else {left = mid + 1;}}return left;}public boolean check(int[][] matrix,int mid,int k,int n){int i = n - 1;int j = 0;int num = 0;while (i >= 0 && j < n){if(matrix[i][j] > mid){i--;}else {num += i + 1;j++;}}return num >= k;}
}

思路:
运用二分查找的思想,在矩阵中寻找一条中间路,把矩阵一分为二,逐渐逼近中间值

6.错误的集合

①哈希表法:

class Solution {public int[] findErrorNums(int[] nums) {int[] res = new int[2];HashMap<Integer, Integer> map = new HashMap<>();for (int num : nums){map.put(num,map.getOrDefault(num,0)+1);}for (int i = 1; i <= nums.length; i++) {int count = map.getOrDefault(i,0);if(count == 0){res[1] = i;}else if(count == 2){res[0] = i;}}return res;}
}

思路:

  1. 把每个数和对应的元素装进哈希表
  2. 最后值为0的是缺的数字,放入res[1];值为2的数字是重复数字,放入res[0].

②数组计数:

class Solution {public int[] findErrorNums(int[] nums) {int n = nums.length;int[] cnts = new int[n + 1];for(int i : nums){cnts[i]++;}int[] res = new int[2];for (int i = 1; i <= n; i++) {if(cnts[i] == 0){res[1] = i;}if(cnts[i] == 2){res[0] = i;}}return res; }
}

思路:
类似哈希表的功能,将缺的数和重复的数记录下来,根据出现的次数来填写新的数组

7.寻找重复数

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。
①哈希表法:

class Solution {public int findDuplicate(int[] nums) {HashMap<Integer, Integer> map = new HashMap<>();for (int i = 0; i < nums.length; i++) {if(map.containsKey(nums[i])){return nums[i];}else {map.put(nums[i],1);}}return 0;}
}

思路:

  1. 把每个数都装进hashmap,如果存在了就直接返回当前数
  2. 找不到就返回0
    结果:

    ②排序再查找:效果不如哈希表
    ③二分查找:
class Solution {public int findDuplicate(int[] nums) {int n = nums.length - 1;int low = 1;int high = n;while (low < high) {int mid = (low + high) >> 1;int count = 0;for (int i = 0; i < nums.length; i++) {if(nums[i] <= mid)count++;}if(count > mid){high = mid;}else {low = mid + 1;}}return low;}
}

思路:
定义前后指针,不断折半缩小查找范围
结果:

④快慢指针:
数组必有重复,可以将其看成一个环形链表:

    class Solution {public int findDuplicate(int[] nums) {int slow = 0,fast = 0;int res = 0;while (true) {slow = nums[slow];fast = nums[nums[fast]];if(slow == fast){fast = 0;while (fast != slow) {slow = nums[slow];fast = nums[fast];}res = fast;break;}}return res;}}

思路:

  1. 定义快慢指针,结果数
  2. 因为总有重复数,可以看作环形链表,快指针走快指针的步数,慢指针走慢指针的步数,总能走到交汇点
  3. slow == fast时,走到了一起,slow从相遇点走,fast从0开始,将相遇在入环点
  4. 最后把fast给结果数返回即可
    结果:

8.数组相邻差值的个数

class Solution {public int[] constructArray(int n, int k) {int[] result = new int[n];int left = 1, right = n, count = 0;while(left <= right){result[count++] = k % 2 != 0 ? left++ : right--;if(k > 1) k--;}return result;}
}

思路:
每次对最小最大先插入,因此前面插入的多半是1, n 或者 n, 1这类形式 再到 2, n - 1或者 n - 1, 2,…,依次类推。
结果:

?9.数组的度

给定一个非空且只包含非负数的整数数组 nums,数组的度的定义是指数组里任一元素出现频数的最大值。

class Solution {public int findShortestSubArray(int[] nums) {HashMap<Integer, Integer> nmap = new HashMap<>();//计算度int du = 0;for (int i : nums) {nmap.put(i, nmap.getOrDefault(i, 0) + 1);du = Math.max(du, nmap.get(i));}//?int ans = Integer.MAX_VALUE;int l = 0, r = 0;HashMap<Integer, Integer> map = new HashMap<>();while (r < nums.length) {map.put(nums[r], map.getOrDefault(nums[r], 0) + 1);while (map.get(nums[r]) == du) {map.put(nums[l], map.get(nums[l]) - 1);ans = Math.min(ans, r - l + 1);l++;}r++;}return ans;}
}

10.托普利茨矩阵

如果矩阵上每一条由左上到右下的对角线上的元素都相同,那么这个矩阵是 托普利茨矩阵 。

class Solution {public boolean isToeplitzMatrix(int[][] matrix) {for (int i = 0; i < matrix.length; i++) {for (int j = 0; j < matrix[0].length; j++) {if(i+1<matrix.length&&j+1<matrix[0].length&&matrix[i][j]!=matrix[i+1][j+1]){return false;}}}return true;}
}

思路:
遍历每行每列的元素,在指针不越界的情况下,对角线元素值不相等就false,否则就true
结果:

11.嵌套数组

数组第一个数对应着下一个索引值,嵌套的数组

class Solution {public int arrayNesting(int[] nums) {int res = 0;for (int i = 0; i < nums.length; i++) {if(nums[i] != Integer.MAX_VALUE){int start = nums[i],count = 0;while (nums[start] != Integer.MAX_VALUE){int temp = start;start = nums[start];count++;nums[temp] = Integer.MAX_VALUE;}res = Math.max(res,count);}}return res;}
}

思路:

  1. 对数组中的每一个数都遍历,只要当前数不是整数最大(其实就是标记的意思,指该数已经讨论过了,因为本题的特点是一个环,只要讨论过的环中的数,就无需再考虑了)标记当前数为起始数,并计数
  2. 开始寻找起始数之后的环,只要当前数的下一个数不是最大数(即标记数),那么将起始数记录并往后推,推的过程同时计环中个数,记录过后对环中所有的数标记为最大数,每遍历完一个环,记录结果,取环的最大值
  3. 循环过后就能找到最大的嵌套数组长度

12.分隔数组

分隔数组,使得对每部分排序后数组就为有序

class Solution {public int maxChunksToSorted(int[] arr) {int max = 0,ans = 0;for (int i = 0; i < arr.length; i++) {max = Math.max(max,arr[i]);if(max == i){ans++;}}return ans;}
}

思路:
一句话就能解这个题:每遍历一个数都记录最大值,只要最大值和当前索引相等就记录一个块

数组与矩阵总结

  1. 数组的优缺点:
    ①优点:可以根据偏移实现快速的随机读写。
    ②缺点:扩容,增删元素极慢。
  2. 二维数组可以以矩阵为表现形式,可以用第二题映射的方法,将二维数组压缩成一维数组,根据索引即可找到所需的值。也可根据画图,将二维数组化成表格形式解决问题

第五章 哈希表

1.两数之和(哈希表做法)

求一个数组中的数之和是否等于目标值

class Solution {public int[] twoSum(int[] nums, int target) {HashMap<Integer, Integer> map = new HashMap<>();for (int i = 0; i < nums.length; i++) {if(map.containsKey(target - nums[i])){return new int[]{map.get(target - nums[i]),i};}map.put(nums[i],i);}return new int[0];}
}

思路:

  1. 利用哈希表,如果map中存在一个加数它的键,此加数用target - nums[i]表示,即该加数的索引值为map.get(target - nums[i]),那么另一个加数的索引值即为i
  2. 直接返回两索引值构成的数组即可
  3. 其余情况就按照(数值,索引值)的键值对装入map

2.存在重复元素(哈希表做法)

给定一个整数数组,判断是否存在重复元素。

class Solution {public boolean containsDuplicate(int[] nums) {HashMap<Integer, Integer> map = new HashMap<>();for (int i : nums){if(!map.containsKey(i)){map.put(i,1);}else {return true;}}return false;}
}

思路:
把每个数值装入哈希表,如果哈希表中有这个数就直接返回true,否则就把这个数添加进表

3.最长和谐子序列

和谐数组是指一个数组里元素的最大值和最小值之间的差别 正好是 1

class Solution {public int findLHS(int[] nums) {HashMap<Integer, Integer> map = new HashMap<>();int res = 0;for (int num : nums){map.put(num,map.getOrDefault(num,0)+1);}for(int key : map.keySet()){if(map.containsKey(key+1)){res = Math.max(res,map.get(key) + map.get(key + 1));}}return res;}
}

思路:
结果就是哈希表中临近键的和的最大值。
例子:[1,3,2,2,5,2,3,7],键值对1->1,2->3,3->2,5->1,7->1
本来是键1和2->对应的最大长度是4,但是2和3对应的最大长度是5,所以结果是5

4.最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
自己的做法:

class Solution {public int longestConsecutive(int[] nums) {if(nums == null || nums.length == 0 || nums.length == 1){return nums.length;}Arrays.sort(nums);truncate(nums);Arrays.sort(nums);int len = 1;int max = 0;for (int i = 0,j = 1; i < nums.length && j < nums.length;) {if(nums[j - 1] + 1 == nums[j]){len = j - i + 1;j++;}else {max = Math.max(max,len);len = 1;j++;i = j - 1;}max = Math.max(max,len);}return max;}public void truncate(int[] arr){for (int i = 0; i < arr.length; i++) {if(i + 1 <arr.length){if(arr[i] == arr[i + 1]){arr[i] = Integer.MAX_VALUE;}}}System.out.println(Arrays.toString(arr));}
}

思路:

  1. 先排除掉特殊情况
  2. 排序去重,这样数组就有序且无重复
  3. 双指针,i为长度起始指针,j为长度终止指针,i一般不动,用j判断当前元素和j的下一元素是否差1,是的话记录长度后再j++;否则记录此时的最大长度,len重置为1,此时j++后再移动指针i,每次判断完都记录一下最大长度。

哈希表做法:

class Solution {public int longestConsecutive(int[] nums) {HashSet<Integer> set = new HashSet<>();for(int num : nums){set.add(num);}int maxlen = 0;for (int num : set){if(!set.contains(num-1)){int currentnum = num;int currentlen = 1;while (set.contains(currentnum+1)){currentnum += 1;currentlen += 1;}maxlen = Math.max(maxlen,currentlen);}}return maxlen;}
}

思路:
将所有数装入set中,set的特点是无序不可重复,对集合中的数进行循环,如果这个数的前一个数不存在就找后面,记录当前数,找后面的数只要有就改变当前数并增加长度,最后取长度的最大值

哈希表总结

1.常使用情景

①需要记录数组中元素及其对应的索引时
②需要记录数组中元素及其对应出现的次数时

2.常用API

void clear():从此映射中移除所有映射关系
boolean containsKey(Object key):如果此映射包含对于指定的键的映射关系,则返回 true。
boolean containsValue(Object value): 如果此映射将一个或多个键映射到指定值,则返回 true。
Set<Map.Entry<K,V>> entrySet():返回此映射所包含的映射关系的 collection 视图。 V get(Object key):返回指定键在此标识哈希映射中所映射的值,如果对于此键来说,映射不包含任何映射关系,则返回 null。
boolean isEmpty():如果此映射不包含键-值映射关系,则返回 true。 Set keySet():返回此映射中所包含的键的set 视图。
V put(K key, V value): 在此映射中关联指定值与指定键。
void putAll(Map<?extends K,? extends V>m):将指定映射的所有映射关系复制到此映射中,这些映射关系将替换此映射目前针对指定映射的所有键的所有映射关系。
V remove(Object key): 如果此映射中存在该键的映射关系,则将其删除。
int size():返回此映射中的键-值映射关系数。
Collection values():返回此映射所包含的值的collection 视图。

3.遍历

取entryset遍历方法有两种:
①通过Iterator来遍历key-value对:

     Set set1 = map.entrySet();Iterator iterator2 = set1.iterator();while (iterator2.hasNext()){System.out.println(iterator2.next());}

②通过foreach遍历:

     HashMap<Integer, Integer> map = new HashMap<>();Set<Map.Entry<Integer, Integer>> entries = map.entrySet();for (Map.Entry<Integer, Integer> entry : entries) {System.out.println(entry.getKey() + " " + entry.getValue());}

遍历Map中的键key:

        Set set = map.keySet();Iterator iterator = set.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}

遍历Map中的value:

        Collection values = map.values();Iterator iterator1 = values.iterator();while(iterator1.hasNext()){System.out.println(iterator1.next());}

第六章 位运算

位运算常用技巧总结

  1. n&(n-1) 去除 n 的位级表示中最低的那一位 1。如:

01011011 &
01011010
01011010

  1. n&(-n) 得到 n 的位级表示中最低的那一位 1。

10110100 &
01001100
00000100

  1. &可以实现掩码操作。 一个数 num 与 mask:00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。

01011011 &
00111100
00011000

  1. | 可以实现设值操作。 一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。

01011011 |
00111100
01111111

  1. ^:异或(异就是1);&:且(有0就是0); | :或(有1就是1)

x^00000000=x x&00000000=0 x|00000000= x
x^11111111=~x x&11111111= x x|11111111=11111111
x^x=0 x&x=x x|x=x

  1. >>:右移1次,相当于除2,右移n次,相当于除n次2,有符号右移左边空位补1。无符号右移左边空位补0。
  2. <<:左移1次,相当于乘2,左移n次,相当于乘n次2,右边移空的部分补0
  3. 要得到只有第 i 位为 1 的 mask,将 1 向左移动 i-1 位即可
  4. 要得到 1 到 i 位为 1 的 mask,(1<<i)-1 即可
  5. 要得到 1 到 i 位为 0 的 mask,对上述取反
  6. java中的位操作:
Integer.bitCount();           // 统计 1 的数量
Integer.highestOneBit();      // 获得最高位
Integer.toBinaryString(int i);     // 转换为二进制表示的字符串`

常用于题目中的数据较为简单,比如数组中就出现几个数,要求给其进行一些数字运算或者逻辑运算的,可能采用位运算的方式效率比较高

1.汉明距离

统计两个数的二进制表示有多少位不同
①最简单的写法:

class Solution {public int hammingDistance(int x, int y) {return Integer.bitCount(x^y);}
}

思路:
两数转为二进制之后,异或统计1的个数即为不同位的个数
②移位,用&找出1的数量

class Solution {public int hammingDistance(int x, int y) {int num = x^y;int count = 0;while (num != 0) {if(num != 0){count += num & 1;num >>= 1;}}return count;}
}

③Brian Kernighan 算法:

class Solution {public int hammingDistance(int x, int y) {int num = x^y;int count = 0;while (num != 0) {if(num != 0){num &= num - 1;count++;}}return count;}
}

思路:
该算法通过数与自身-1的数相与不断将最低位的1置0,每置一次0,相当于删除一次1。结果最理想!

2.只出现一次的数字

速解:相同数异或得0

class Solution {public int singleNumber(int[] nums) {int res = 0;for (int i : nums){res ^= i;}return res;}
}

数组中唯一一个不重复的元素
思路:考察异或的用法
任何数和 00 做异或运算,结果仍然是原来的数,即 a ^ 0 = a,a ⊕ 0 = a。
任何数和其自身做异或运算,结果是 00,即 a ^ a = 0,a ⊕ a = 0。

3.丢失的数字

速解:比如数字9他的数组要找一边索引再找一遍数组中的数,用异或就找到了丢失的数(相同数异或=0)
给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。

class Solution {public int missingNumber(int[] nums) {int missing = nums.length;for (int i = 0; i < nums.length; i++) {missing ^= i ^ nums[i];}return missing;}
}

思路:
将结果先假设为数组长度,即假设丢失的数就是数组长度的数,用假设的数与每一对索引值和数组值异或的结果再异或,即可得到丢失的数

4.数组中不重复的两个元素

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

class Solution {public int[] singleNumber(int[] nums) {int res = 0;for (int num : nums){res ^= num;}res &= -res;int[] rets = {0,0};for (int num : nums){if((res & num) == 0){rets[0] ^= num;}else {rets[1] ^= num;}}return rets;}
}

思路:

  1. 是上一题的进阶版。上一题是只有一个单独数,本题是两个。
  2. 可以通过上述方法,但得到的结果是两不同数的异或值,怎么得到这两个数
  3. 通过异或值为1的最低位判断,因为异或最低位为1,说明两数不同,通过 (技巧中的2) 公式来得到
  4. 通过判断每一个数和最低位为1的数将两个不同的数分开,分开装入数组中,即可得到结果

5.翻转一个数的比特位

class Solution {public int reverseBits(int n) {int res = 0;for (int i = 0; i < 32 && n != 0; ++i) {res |= (n & 1) << (31 - i);n >>>= 1;}return res;}
}

思路:
将n的每一位取出左移至该放的位置,记录当前结果,每次移动后再将n无符号右移1位,确保下次是n的下一位

简单做法:

class Solution {public int reverseBits(int n) {return Integer.reverse(n);}
}

6.不用额外变量交换两个整数

class Solution {public int[] exchange(int a,int b) {a = a ^ b;b = a ^ b;a = a ^ b;return  new int[]{a,b};}
}

思路:使用异或可以完成两数无额外空间的互换

7. 2的幂

给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。

class Solution {public boolean isPowerOfTwo(int n) {if(n < 0){return false;}return Integer.bitCount(n) == 1;}
}

思路:2的幂的二进制都是只包含一个1的数,除了-2147483648特殊,剩下的只要二进制不是含有一个1的都不是2的幂
结果:

8. 4的幂

给定一个整数,写一个函数来判断它是否是 4 的幂次方。如果是,返回 true ;否则,返回 false 。

class Solution {public boolean isPowerOfFour(int n) {if(Integer.bitCount(n) != 1 || n < 0){return false;}String s = Integer.toBinaryString(n & (-n));for (int i = s.length() - 1; i >= 0; i--) {if(s.charAt(i) != 0){if(i % 2 == 0){return true;}else {return false;}}}return true;}
}

思路:

  1. 负数或者二进制中不是含一个1的都排除掉
  2. 取该数二进制的最低位1,判断这一位是不是在奇数索引的位置上,倒叙保证了从右往左不错判 i 的位置
  3. 结果接近双百

其他算法:
①对3取模值为1的数一定是4的幂

class Solution {public boolean isPowerOfFour(int n) {return n > 0 && (n & (n - 1)) == 0 && n % 3 == 1;}
}

②将 n 和 十六进制 0xaaaaaaaa 进行按位与运算,如果结果为 0,说明 n 二进制表示中的 1 出现在偶数的位置,否则说明其出现在奇数的位置。

class Solution {public boolean isPowerOfFour(int n) {return n > 0 && (n & (n - 1)) == 0 && (n & 0xaaaaaaaa) == 0;}
}

9.交替位二进制数

判断一个数的位级表示是否不会出现连续的 0 和 1

class Solution {public boolean hasAlternatingBits(int n) {String s = Integer.toBinaryString(n);return !s.contains("00") && !s.contains("11");}
}

思路:
取得该数二进制的字符串,如果包含00和11就挂

class Solution {public boolean hasAlternatingBits(int n) {if(n == 1431655765)return true;int i = ((n >> 1) ^ n) + 1;int i1 = i & (i - 1);return i1 == 0;}
}

思路:

把原数右移一位,两数异或再加一,消去这个唯一,如果结果是0就是交替位的二进制数

class Solution {public boolean hasAlternatingBits(int n) {if(n == 1431655765)return true;int i = ((n >> 1) ^ n) + 1;return Integer.bitCount(i) == 1;}
}

想法诞生如上图

10.求一个数的补数

速解:反数&(最高位左移-1)
即该数二进制的反码表示的数

class Solution {public int findComplement(int num) {return ~num & ((Integer.highestOneBit(num) << 1) - 1);}
}

思路:用例子说明
例子:5

  1. 5的二进制编码为 0000 0000 0000 0000 0000 0000 0000 0101
  2. ~5的二进制编码为 1111 1111 1111 1111 1111 1111 1111 1010
  3. 找到5的最高位为1的二进制是 0000 0000 0000 0000 0000 0000 0000 0100
  4. 将这个最高位左移一位 - 1得:0000 0000 0000 0000 0000 0000 0000 0111
  5. 与步骤一掩码:0000 0000 0000 0000 0000 0000 0000 0010,得其补数2

11. 两数之和(不能用运算符)

class Solution {public int getSum(int a, int b) {while (b != 0) {int carry = (a & b) << 1;a = a ^ b;b = carry;}return a;}
}
public int getSum(int a, int b) {return b == 0 ? a : getSum((a ^ b), (a & b) << 1);
}

思路:

  1. 首先计算进位,&是掩码操作,如果有两个1& 那就是1,说明有进位,左移一位就代表了进位
  2. 异或操作可以看作是没有进位的加法,所以ab异或
  3. 把b令作进位,循环不断和异或的结果“相加”,只要a与b没有进位,返回a就是答案

12.最大单词长度乘积

给定一个字符串数组 words,找到 length(word[i]) * length(word[j]) 的最大值,并且这两个单词不含有公共字母。你可以认为每个单词只包含小写字母。如果不存在这样的两个单词,返回 0。

class Solution {public int maxProduct(String[] words) {int[] wordNum = new int[words.length];  // 存储每个单词的字符出现情况,用位表示for(int i = 0; i < words.length; i++) {String word = words[i];int num = 0;  // 每个单词的对应数字(每个位存储字母出现情况后的整数)for(int j = 0; j < word.length(); j++) {int carry = word.charAt(j)-'a';num = num | (1 << carry);}wordNum[i] = num;}int max = 0;  // 最终最长乘积// 循环遍历,对比每两个单词for (int i = 0; i < words.length - 1; i++){for (int j = i + 1; j < words.length; j++){// 两个单词有相同字符的话,对应wordNum取&,会存在1&1的情况,则肯定不为0if ((wordNum[i] &  wordNum[j]) == 0 ){  // 如果两个单词没有相同字符,则进行计算长度乘积// 更新最长乘积max = Math.max(max,words[i].length() * words[j].length());}}}return max;}
}

思路:

  1. 将每个单词转化为数。构造相同长度的int数组,将每个字符串表示为字符型,将每个单词的字符减去字符a,记为carry
  2. 将1移动carry次,说明把这个字符移动到了二进制编码中非重复的部分
  3. 最后通过每个单词的每个字符之间按位与是否为0,从而确定两个单词是否有重复的单词,并记录最大长度

13.比特位计数

统计从 0 ~ n 每个数的二进制表示中 1 的个数

class Solution {public int[] countBits(int n) {int[] ans = new int[n + 1];for (int i = 1; i < ans.length; i++) {ans[i] = Integer.bitCount(i);}return ans;}
}

思路:给数组每个位置的数赋予它二进制为1个数的值

class Solution {public int[] countBits(int n) {int[] ans = new int[n + 1];for (int i = 1; i < ans.length; i++) {ans[i] = countOne(i);}return ans;}private int countOne(int i) {int ones = 0;while (i > 0) {i &= (i - 1);ones++;}return ones;}
}

利用Brian Kernighan 算法,x = x & (x−1),该运算将 x 的二进制表示的最后一个 1 变成 0

刷题学习—数据结构(字符串、栈和队列、链表、数组与矩阵、哈希表、位运算)相关推荐

  1. JAVA day16、17 数据结构(栈、队列、数组、链表、红黑树)

    一.什么叫数据结构? 数据结构是相互之间存在一种或多种特定关系的数据元素的集合,即带"结构"的数据元素的集合."结构"就是指数据元素之间存在的关系,分为逻辑结构 ...

  2. Java笔记整理五(Iterator接口,泛型,常见数据结构(栈,队列,数组,链表,红黑树,集合),jdk新特性,异常,多线程,Lambda表达式)

    Java笔记整理五 1.1Iterator接口 Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象 ...

  3. [LeetCode]-Python刷题第三周(栈和队列)

    20. Valid Parentheses 合法括号(Easy) Given a string containing just the characters '(', ')', '{', '}', ' ...

  4. Java基础加强重温_05:Iterator迭代器、增强for循环、集合综合案例-斗地主、数据结构(栈、队列、数组、链表、红黑树)、List接口、Set接口

    摘要: Java基础加强重温_05: Iterator迭代器(指针跟踪元素). 增强for循环(格式.底层). 集合综合案例-斗地主(代码规范抽取代码,集合元素打乱). 数据结构[栈(先进后出,子弹夹 ...

  5. 数据结构:栈、队列、数组、链表、红黑树结构的特点

    * 1.栈结构:特点:先进后出,类似子弹夹 * 2.队列的结构: 特点:先进先出 * 3.数组结构: 特点:查询快,增删慢 * 为什么数组查询快? 因为数组的地址是连续的,我们可以通过数组的首地址查到 ...

  6. 18版考研数据结构天勤课后习题代码-数组、矩阵与广义表【完】

    #include <iostream> using namespace std; #define maxSize 101 /* //把非零元素移动到数组前端  天勤P122(二)1 voi ...

  7. 简单的数据结构介绍(栈、队列、数组、链表、红黑树)

    学习目标: 了解常见的数据结构 学习内容: 栈.队列.数组.链表.红黑树 学习产出: 1. 数据结构 数据结构 : 就是数据用什么样的方式组合在一起 常见的数据结构有: 栈, 队列, 数组, 链表和红 ...

  8. C语言【数据结构】栈和队列【OJ题(C++)、选择题】

    目录 一.OJ题 1.225. 用队列实现栈 2.232. 用栈实现队列 3.622. 设计循环队列 4.20. 有效的括号 二.选择题 1.下列关于栈的叙述正确的是(B) 2.一个栈的入栈序列为AB ...

  9. 数据结构与算法第二章 线性表、栈、队列、数组、字符串、树、二叉树、哈希表的增删查

    03 增删查:掌握数据处理的基本操作,以不变应万变 通过前面课时的学习,相信你已经建立了利用数据结构去完成时空转移的思想.接下来,你需要在理论思想的指导下灵活使用.其实,要想灵活使用数据结构,你需要先 ...

  10. 数据结构栈队列链表数组

    目录: 数据结构 栈(stack) 队列 链表 数组 数据结构 数据结构是什么 简单来说,数据结构就是设计数据以何种方式存储在计算机中 比如:列表,集合,与字典等都是一种数据结构 程序 = 数据结构 ...

最新文章

  1. 物联网平台 源码_国内首个智慧交通物联网平台发布
  2. 用 vue 写小程序,基于 mpvue 框架重写 weui
  3. Eclipse快捷键指南
  4. 通过踩坑带你读透虚拟机的“锁粗化”
  5. SpringMVC中@RequestMapping 6个基本用法小结
  6. 如何把睡袋转给别人_微信收到的语音如何转给别人?试试这2个方法,没准能帮到你...
  7. easyui数据请求两个url_easyui使用是调用两次后台请求(解决)
  8. action mutation 调用_Vuex源码学习(六)action和mutation如何被调用的(前置准备篇)...
  9. 激战服务器位置,如何选服务器 《激战2》服务器设置讲解
  10. 计算机平面设计专业有哪些课程,计算机平面设计专业课程有哪些?
  11. OC----预处理器
  12. platform_get_resource的分析
  13. atoi,itoa,strcpy, strcmp,strcpy, strcpy_s, memc...
  14. 计算机自动关机原理,电脑自动关机什么原因 电脑自动关机是怎么回事
  15. PHP程序员全栈,PHP程序员画的 “全栈工程师技能树” 思维导图
  16. 使用viewer.js实现在线浏览Office文档
  17. STA series --- 6 .Crosstalk and Noise
  18. 韩顺平Linux视频教程—笔记(全)
  19. 高项_第七章项目成本管理
  20. 关于存储优化型实例和大型数据仓库EC2实例选型

热门文章

  1. 2019最佳硬盘:台式机和笔记本电脑的顶级硬盘
  2. oracle 报错904,EXP-00008: 遇到 ORACLE 错误 904
  3. Racket编程指南——1 欢迎来到Racket!
  4. SM系列国密算法简介
  5. 老狗——python求中位数
  6. 软件测试之逻辑思维题
  7. 板凳——————————————————c++(104)
  8. GoLand每次切换,光标跑到行首
  9. android怎么测试网速,怎样用手机测网速 安卓手机测网速的方法推荐
  10. 纽迪瑞科技入选快公司FastCompany最具创新力榜单:科技与制造TOP30