一、字符串相关题目
1.1 字符串循环移位包含
给定两个字符串 s1 和 s2,要求判定 s2 是否能够被 s1 做循环移位得到的字符串包含。
解答:s1 进行循环移位的结果是 s1s1 的子字符串,因此只要判断 s2 是否是 s1s1 的子字符串即可。
String str=str1+str1;
for(int i=0;i<str1.length();i++){//没必要遍历STR中每一个字符
if(str.charAt(i)==str2.charAt(0)){
if(str.substring(i,i+str2.length()).equals(str2)){
return true;
}
}
}
return false;
1.2 字符串循环移位
解答:循环移位的结果一定是str+str的子串,所以只需要从拼接的长字符串中截取即可
public static String loopTran(String str,int k){
String combineStr=str+str;
return combineStr.substring(str.length()-k,str.length()*2-k);
}
1.3 字符串中单词的翻转
解答:按照空格分割后,倒序拼接
public static String worldRverse(String str){
String[] strings=str.split(" ");
StringBuilder stringBuilder=new StringBuilder();
for(int i=strings.length-1;i>=0;i--){
stringBuilder.append(strings[i]+" ");
}
return stringBuilder.toString();
}
1.4 两个字符串包含的字符是否完全相同
力扣地址:https://leetcode-cn.com/problems/valid-anagram/description/
解答:可以用 HashMap 来映射字符与出现次数,然后比较两个字符串出现的字符数量是否相同。(自己采用的方法,比较繁琐,空间复杂度高)
由于本题的字符串只包含 26 个小写字符,因此可以使用长度为 26 的整型数组对字符串出现的字符进行统计,不再使用 HashMap。
public static boolean isAnagram(String s, String t) {
int[] times= new int[26];
for(char c:s){
times[c-'a']++;
}
for(char c:t){
times[c-['a']]--;
}
for(int temp:times){
if(temp!=0){
return false;
}
}
return true;
}
1.5 计算一组字符集合可以组成的回文字符串的最大长度
力扣地址:https://leetcode-cn.com/problems/longest-palindrome/description/
解答:考虑到只有大小写字母,所以采用长度为128的长度的数组做统计每个字符出现的次数,每个字符有偶数个可以用来构成回文字符串。因为单个字符可以放在中间构成回文,所以最终构成的回文长度与原字符串长度应该做比较,如果长度不同,说明应该加1
public static int longestPalindrome(String s) {
int[] times=new int[128];
for(char c:s){
times[c]++;
}
int res=0;
for(int i=0;i<128;i++){
res+=(times[i]/2)*2;
}
return res==s.length()?res:++res;//注意不是res++
}
1.6 字符串同构
力扣地址:https://leetcode-cn.com/problems/isomorphic-strings/description/
解答:如果两个字符上次出现的位置一样,那么两个字符属于同构。通过两个数组,对比数组中的数值可以代表字符对应关系
public boolean isIsomorphic(String s, String t) {
int[] indexOfS=new int[128];
int[] indexOfT=new int[128];
for(int i=0;i<s.length();i++){
char sc=s.charAt(i);
char tc=t.charAt(i);
if(indexOfS[sc]!=indexOfT[tc]){
return false;
}
indexOfS[sc]++;
indexOfT[tc]++;
}
return true;
}
1.7 回文子字符串个数
力扣地址:https://leetcode-cn.com/problems/palindromic-substrings/description/
解答:可以采用暴力算法,遍历出所有的子串,然后再去查看每个子串是否回文。
可以以一个字符作为回文中心,向两边扩展,如果扩展过程中显示两个字符不相同,则不回文。不过扩展过程中要考虑奇数长度和偶数长度的区别
private int count=0;
public int countSubstrings(String s){
for(int i=0;i<s.length();i++){
extendSubstrings(i,i,s);
extendSubstrings(i,i+1,s);
}
}
private void extendSubstrings(int start,int end,String s){
while(start>=0 && end<s.length() && s.charAt(start)==s.charAt(end)){
start--;
end++;
count++;
}
}
1.8 判断一个整数是否是回文数
要求:不使用额外空间,不许将整数转换为字符串
力扣地址:https://leetcode-cn.com/problems/palindrome-number/description/
解答:可以利用数学运算把输入翻转,然后去比较翻转和输入是否相等即可。
**改进:**没必要翻转全部,只需要翻转输入数据的后一半,将后一半与前一半进行比较。
public boolean isPalindrome(int x){
if(x==0){
return true;
}
if(x<0 || x%10==0){
return false;
}
int right=0;
while(x>right){
right=right*10+x%10;
x/=10;
}
return x==right || x==right/10;
}
1.9 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数
力扣地址:https://leetcode-cn.com/problems/count-binary-substrings/description/
解答:我们可以将字符串 s 按照 0 和 1 的连续段分组,存在counts 数组中,例如 s = 00111011,可以得到这样的 counts 数组= {2, 3, 1, 2}。这里counts 数组中两个相邻的数一定代表的是两种不同的字符。假设数组中两个相邻的数字为 u 或者 v,它们对应着 u 个0 和 v 个 1,或者 u 个 1 和 v 个 0。它们能组成的满足条件的子串数目为 min{u,v},即一对相邻的数字对答案的贡献。
public static int countBinarySubstrings(String s) {
int len=s.length();
int temp=0;
List<Integer> list=new ArrayList<>();
while(temp<n){
int count=0;
char c=s.charAt(temp);
while(temp<n && s.charAt(temp)==c){
++count;
++temp;
}
list.add(count);
}
int res=0;
for (int i=1;i<list.size();i++){
res+=Math.min(list.get(i),list.get(i-1));
}
return res;
}
1.10 最长不重复子串
力扣地址:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/
解答:
public int lengthOfLongestSubstring(String s) {
if(s==null || s.length()==0) return 0;
Set<Character> set=new HashSet<>();
int res=0;
for(int i=0;i<s.length();i++){
set.add(s.charAt(i));
int j=i+1;
while(j<s.length() && !set.contains(s.charAt(j))){
set.add(s.charAt(j));
j++;
}
res=Math.max(res,set.size());
set.clear();
}
return res;
}
1.11 验证回文字符串
力扣连接:https://leetcode-cn.com/problems/valid-palindrome-ii/
解答:
public boolean validPalindrome(String s) {
if(s==null || s.length()==0){
return false;
}
int left=0;
int right=s.length()-1;
while(left<right){
if(s.charAt(left)!=s.charAt(right)){
return isPalindrome(s.substring(left,right)) || isPalindrome(s.substring(left+1,right+1));
}
left++;
right--;
}
return true;
}
public boolean isPalindrome(String s){
if(s==null || s.length()==0){
return false;
}
int left=0;
int right=s.length()-1;
while(left<right){
if(s.charAt(left)!=s.charAt(right)){
return false;
}
left++;
right--;
}
return true;
}
1.12 最长公共前缀
力扣地址:https://leetcode-cn.com/problems/longest-common-prefix/
字符串A,B,C的最长公共前缀,我求出A,B的最长公共前缀后,求这个最长公共前缀和C的最长公共前缀,便得到了最终答案
public String longestCommonPrefix(String[] strs) {
if(strs==null || strs.length==0){
return "";
}
if(strs.length==1) return strs[0];
String prefix=prefix(strs[0],strs[1]);
int i=2;
while(i<strs.length){
prefix=prefix(prefix,strs[i]);
i++;
}
return prefix;
}
private String prefix(String str1,String str2){
if(str1==null || str2==null || str1.length()==0 || str2.length()==0){
return "";
}
int l1=str1.length();
int l2=str2.length();
StringBuilder sb=new StringBuilder();
int i=0;
int j=0;
while(i<l1 && j <l2){
if(str1.charAt(i)!=str2.charAt(j)){
return sb.toString();
}else{
sb.append(str1.charAt(i));
i++;
j++;
}
1.12 字符换变形
牛客地址:https://www.nowcoder.com/questionTerminal/c3120c1c1bc44ad986259c0cf0f0b80e?answerType=1&f=discussion
解答:
public String trans(String s, int n) {
//空格划分成数组
String[] arr = s.split(" ",-1);
StringBuilder sb = new StringBuilder();
for(int i=arr.length-1;i>=0;i--){
if(i==0){
sb.append(arr[i]);
}else{
sb.append(arr[i]+" ");
}
}
return reverse(sb.toString());
}
public String reverse(String s){
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char temp = s.charAt(i);
if (temp >= 'a' && temp <= 'z') {
temp = (char)(temp-32);
}else if(temp >= 'A' && temp <= 'Z') {
temp = (char)(temp+32);
}
sb.append(temp);
}
return sb.toString();
}
二、链表相关题目
2.1 找出两个链表的交点
力扣地址:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/description/
要求: 时间复杂度 O(n)
、仅用 O(1)
内存的解决方案 。链表必须 保持其原始结构
解答:
A: a1 → a2 ↘ c1 → c2 → c3 ↗B: b1 → b2 → b3
设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode l1=headA;
ListNode l2=headB;
while(l1!=l2){
l1=(l1==null):headB?l1.next;
l2=(l2==null):headA?l2.next;
}
return l1;
}
2.2 链表反转
力扣地址:https://leetcode-cn.com/problems/reverse-linked-list/description/
解答:
递归
public ListNode reverseList(ListNode head) {
if(head==null||head.next==null){
return head;
}
ListNode next=head.next;
ListNode newHead=reverseList(next);
next.next=head;
head.next=null;
return newHead;
}
头插法
public ListNode reverseList(ListNode head) {
ListNode newHead=new ListNode(-1);
ListNode next=null;
while(head!=null){
next=head.next;
head.next=newHead.next;
newHead.next=head;
head=next;
}
return newHead.next;
}
2.3 归并两个有序的链表
力扣地址:https://leetcode-cn.com/problems/merge-two-sorted-lists/description/
解答:递归
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(l1.next,l2);
return l1;
}else{
l2.next=mergeTwoLists(l1,l2.next);
return l2;
}
}
2.4 从有序链表中删除重复节点
力扣地址:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/description/
解答:
头插法:
public ListNode deleteDuplicates(ListNode head) {
ListNode tempNode=new ListNode(-1);
tempNode.next=head;
if(head==null){
return null;
}
while(head!=null && head.next!=null){
if(head.val==head.next.val){
head.next=head.next.next;
}else{
head=head.next;
}
}
return tempNode.next;
}
递归(最优解法)
public ListNode deleteDuplicates(ListNode head) {
if(head==null || head.next==null){
return head;
}
head.next=deleteDuplicates(head.next);
return head.var==head.next.val?head.next:head;
}
2.5 删除链表的倒数第 n 个节点
力扣地址:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/description/
解答:定位链表第N个节点,可以采用快慢指针法。(涉及到定位链表的节点的,都采用快慢指针法)
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode newhead=new ListNode(-1,head);//引入虚假头结点,解决链表长度为1时的问题
ListNode fast=head;
ListNode slow=newhead;
while(n-->0){
fast=fast.next;
}
while(fast!=null){
slow=slow.next;
fast=fast.next;
}
slow.next=slow.next.next;
return newhead.next;
}
2.6 交换链表中的相邻结点
力扣地址:https://leetcode-cn.com/problems/swap-nodes-in-pairs/description/
解答:
递归(最优解法)
public ListNode swapPairs(ListNode head) {
if(head==null || head.next==null){
return head;
}
ListNode next=head.next;
head.next=swapPairs(next.next);
next.next=head;
return next;
}
2.7 链表求和
力扣地址:https://leetcode-cn.com/problems/add-two-numbers-ii/description/
解法:
Stack<Integer> stack1=new Stack<>();
Stack<Integer> stack2=new Stack<>();
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
buildStack(l1,stack1);
buildStack(l2,stack2);
ListNode newhead=new ListNode(-1);
int carry=0;
while(!stack1.isEmpty() || !stack2.isEmpty() || carry!=0){
int data1=stack1.isEmpty()?0:stack1.pop();
int data2=stack2.isEmpty()?0:stack2.pop();
carry+=data1+data2;
ListNode node =new ListNode(carry%10);
node.next=newhead.next;
newhead.next=node;
carry/=10;
}
return newhead.next;
}
private void buildStack(ListNode head,Stack stack){
while(head!=null){
stack.push(head.val);
head=head.next;
}
}
2.8 回文链表
力扣地址:https://leetcode-cn.com/problems/palindrome-linked-list/description/
要求:O(n) 时间复杂度和 O(1) 空间复杂度
解答:先用快慢指针找到链表的中心,然后切分,将后半段链表翻转,进行比较,最后将链表复原,也就是说不改变链表的原有结构
public boolean isPalindrome(ListNode head) {
if(head==null){
return false;
}
ListNode firstHalfEnd=cutNode(head);
ListNode secondHalfEnd=reverse(firstHalfEnd);
ListNode p1=head;
ListNode p2=secondHalfEnd;
while(p2!=null && p1!=null){
if(p1.val != p2.val){
return false;
}
p1=p1.next;
p2=p2.next;
}
return true;
}
private ListNode cutNode(ListNode head) {
ListNode fast=head.next;
ListNode slow=head;
while(fast!=null && fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
ListNode next=slow.next;
slow.next=null;
return next;
}
private ListNode reverse(ListNode head) {
ListNode newhead=new ListNode(-1);
while(head!=null){
ListNode next=head.next;
head.next=newhead.next;
newhead.next=head;
head=next;
}
return newhead.next;
}
2.9 分割链表
力扣地址:https://leetcode-cn.com/problems/split-linked-list-in-parts/description/
解答:先求得链表的长度,然后每个部分先放len/k,然后前len%k部分的元素数再加1.
public ListNode[] splitListToParts(ListNode root, int k) {
int N=0;
ListNode cur=root;
while(cur!=null){
++N;
cur=cur.next;
}
int width=N/k;
int addOne=N%k;
cur=root;
ListNode[] result=new ListNode[k];
for(int i=0;i<k;++i){
ListNode temp=new ListNode(-1);
ListNode write=temp;
int cursize=width+(addOne>0?1:0);
for(int j=0;j<cursize;j++){
write.next=new ListNode(cur.val);
write=write.next;
if(cur!=null){
cur=cur.next;
}
}
result[i]=temp.next;
--addOne;
}
return result;
}
2.10 链表元素按奇偶聚集
力扣地址:https://leetcode-cn.com/problems/odd-even-linked-list/description/
解答:奇偶聚集,那就将原始链表按奇偶分为两个链表,最后再将奇偶链表合并。
public ListNode oddEvenList(ListNode head) {
if(head == null || head.next==null){
return head;
}
ListNode odd=head;
ListNode even=head.next;
ListNode evenHead=even;
while(even!=null && even.next!=null){
odd.next=odd.next.next;
odd=odd.next;
even.next=even.next.next;
even=even.next;
}
odd.next=evenHead;
return head;
}
2.11 旋转链表
力扣地址:https://leetcode-cn.com/problems/rotate-list/
解答:K的值可能会大于链表的长度len,所以K要对len取余,如果旋转k为n*len,则链表其实不变,不用处理。最终再采用双指针法进行定位。
public ListNode rotateRight(ListNode head, int k) {
if(head==null || head.next==null){
return head;
}
int len=0;
ListNode temp=head;
while(temp!=null){
len++;
temp=temp.next;
}
if(k%len==0){
return head;
}
int real=k%len;
ListNode fast=head;
ListNode slow=head;
while(real-->0){
fast=fast.next;
}
while(fast.next!=null){
slow=slow.next;
fast=fast.next;
}
ListNode newhead=slow.next;
slow.next=null;
fast.next=head;
return newhead;
}
2.12 判断链表是否有环,如果有环,找出环的入口
力扣:https://leetcode-cn.com/problems/linked-list-cycle/
解答:
public boolean hasCycle(ListNode head) {
if(head ==null || head.next==null) return false;
ListNode slow=head;
ListNode fast=head.next;
while(fast!=null && fast.next!=null){
if(fast==slow){
return true;
}
slow=slow.next;
fast=fast.next.next;
}
return false;
}
public ListNode detectCycle(){
ListNode fast=this.head;
ListNode slow=this.head;
//先找到第一次相遇的时候
while(fast!=null && fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if(fast==slow){
break;
}
}
if(fast==null || fast.next==null){
return null;
}
//让slow或者fast到头那里
slow=this.head;
while(slow!=fast){
slow=slow.next;
fast=fast.next;
}
return slow;
}
2.13 单链表前后交叉排序
力扣地址:https://leetcode-cn.com/problems/reorder-list/
解答:先分割,后反转,最后再拼接
public void reorderList(ListNode head) {
if(head==null || head.next==null){
return;
}
ListNode mid=cut(head);
ListNode sec=reverse(mid.next);
mid.next=null;
ListNode cur=head;
ListNode temp=sec.next;
while(true){
sec.next=cur.next;
cur.next=sec;
if(temp==null){
return;
}
cur=sec.next;
sec=temp;
temp=temp.next;
}
}
private ListNode cut(ListNode head){
if(head==null || head.next==null){
return head;
}
ListNode slow=head;
ListNode fast=head.next;
while(fast!=null && fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
private ListNode reverse(ListNode head){
if(head==null || head.next==null){
return head;
}
ListNode next=head.next;
ListNode newhead=reverse(next);
next.next=head;
head.next=null;
return newhead;
}
2.14 K个一组翻转链表
链表:https://leetcode-cn.com/problems/reverse-nodes-in-k-group/
解答:
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy=new ListNode(-1);
dummy.next=head;
ListNode pre=dummy;
ListNode end=dummy;
while(end.next!=null){
for(int i=0;i<k && end!=null;i++){
end=end.next;
}
if(end==null){
break;
}
ListNode start=pre.next;
ListNode next=end.next;
end.next=null;
pre.next=reverse(start);
start.next=next;
pre=start;
end=pre;
}
return dummy.next;
}
public static ListNode reverse(ListNode head){
if(head==null || head.next==null){
return head;
}
ListNode newhead=new ListNode(-1);
while(head!=null){
ListNode next=head.next;
head.next=newhead.next;
newhead.next=head;
head=next;
}
return newhead.next;
}
2.15 之字形层序遍历二叉树
力扣地址:https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/submissions/
解答:
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res=new ArrayList<>();
if(root==null){
return res;
}
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
int i=1;
while(!queue.isEmpty()){
int size=queue.size();
List<Integer> list=new ArrayList<>();
for(int j=0;j<size;j++){
TreeNode node=queue.poll();
list.add(node.val);
if(node.left!=null){
queue.offer(node.left);
}
if(node.right!=null){
queue.offer(node.right);
}
}
if(i%2==1){
res.add(new ArrayList<>(list));
}else{
Collections.reverse(list);
res.add(new ArrayList<>(list));
}
i++;
}
return res;
}
2.16 单链表排序
力扣地址:https://leetcode-cn.com/problems/sort-list/
解答:思路就是插入排序
public ListNode sortList(ListNode head) {
if(head==null || head.next==null){
return head;
}
ListNode dummy=new ListNode(Integer.MIN_VALUE);
while(head!=null){
ListNode next=head.next;
head.next=null;
ListNode pre=dummy;
ListNode dummyNext=dummy.next;
if(dummyNext==null){
pre.next=head;
}else{
while(dummyNext!=null){
if(pre.val<=head.val && dummyNext.val>=head.val){
pre.next=head;
head.next=dummyNext;
break;
}
pre=dummyNext;
dummyNext=dummyNext.next;
}
if(dummyNext==null){
pre.next=head;
}
}
head=next;
}
return dummy.next;
}
2.17 链表局部反转
地址:https://leetcode-cn.com/problems/reverse-linked-list-ii/
public ListNode reverseBetween(ListNode head, int left, int right) {
// 设置 dummyNode 是这一类问题的一般做法
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode pre = dummyNode;
for (int i = 0; i < left - 1; i++) {
pre = pre.next;
}
ListNode cur = pre.next;
ListNode next;
for (int i = 0; i < right - left; i++) {
next = cur.next;
cur.next = next.next;
next.next = pre.next;
pre.next = next;
}
return dummyNode.next;
}
三、 排序
参考博客:https://zhuanlan.zhihu.com/p/34168443
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wwnY5yFm-1652019362988)(E:\java笔记\PIC\刷题笔记.assets\image-20210811104228230.png)]
3.1 冒泡排序
基本思想:冒泡排序(Bubble Sort)是一种简单的排序算法。它重复访问要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。访问数列的工作是重复地进行直到没有再需要交换的数据,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端,像水中的气泡从水底浮到水面。
public static void bubbleSort(int[] array){
int tmp;
boolean flag = false; //设置是否发生交换的标志
for(int i = array.length-1;i >= 0;i--){
for(int j=0;j<i;j++){
//每一轮都找到一个最大的数放在右边
if(array[j]>array[j+1]){
swap(j,j+1);
flag = true;
//发生了交换
}
}
if(!flag) break; //这一轮循环没有发生交换,说明排序已经完成,退出循环
}
}
public static void swap(int[] array, int i, int j) {
int temp=array[i];
array[i]=array[j];
array[j]=temp;
}
3.2 快速排序
基本思想:快速排序(Quicksort)是对冒泡排序的一种改进,借用了分治的思想,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
public void quickSort(int a[],int low,int high){
int pivot = 0;// pivot:中心
if(low < high){
//将数组一分为二
pivot = partition(a,low,high); //对第一部分进行递归排序
quickSort(a,low,pivot-1); //对第二部分进行递归排序
quickSort(a,pivot + 1,high);
}
} //partition函数,将原始数组分割
public int partition(int a[],int low,int high){
int symbol=a[low];
while(low<high){
while(low<high && a[high]>=symbol){
high--;
}
a[low]=a[high];
while(low<high && a[low]<=symbol){
low++;
}
a[high]=a[low];
}
a[low]=symbol;
return low;
}
3.3 插入排序
基本思想: 每步将一个待排序的纪录,按其码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。
public static void insertionSort(int[] array){ for(int i=0;i<array.lenght;i++){ int temp=array[i]; int j=i; while(j>0 && array[j-1]>temp){ array[j]=array[j-1]; } array[j]=temp; }}
3.4 选择排序
基本思想: 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
public static void selectSort(int[] array){ for(int i=0;i<array.length;i++){ int min=array[i]; int minIndex=i; int j=i; while(j<array.length){ if(array[j]<min){ min=array[j]; minIndex=j; } ++j; } if(minIndex!=i){ array[minIndex]=array[i]; array[i]=min; } } }
3.5 希尔排序
3.6 堆排序
预备知识:堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b23psXrj-1652019362989)(刷题笔记.assets/1623809985978.png)]
我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子 :
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y9dvs1g0-1652019362989)(刷题笔记.assets/1623810024118.png)]
该数组从逻辑上讲就是一个堆结构。
基本思路: 将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
public static void sort(int []arr){
for(int i=arr.length/2-1;i>=0;i--){
adjustHeap(arr,i,arr.length);
}
for(int i=arr.length-1;i>0;i--){
swap(arr,0,i);
adjustHeap(arr,0,i);
}
}
public static void adjustHeap(int []arr,int i,int length){
int temp=arr[i];
for(int k=2*i+1;k<length;k=2*k+1){
if(k+1<length && arr[k]<arr[k+1]){
k++;
}
if(arr[k]>tmp){
arr[i]=arr[k];
i=k;
}else{
break;
}
}
arr[i]=temp;
}
public static void swap(int []arr,int a ,int b){
int temp=arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
Kth Element问题可以用堆排序来解决
力扣地址:https://leetcode-cn.com/problems/kth-largest-element-in-an-array/description/
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq=new PriorityQueue<>();//小顶堆
for(int val:nums){
pq.add(val);
if(pq.size()>k){
pq.poll();
}
}
return pq.peek();
}
3.7 归并排序
基本思想: 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略。
public static void sort(int []arr){
int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
sort(arr,0,arr.length-1,temp);
}
private static void sort(int[] arr,int left,int right,int []temp){
if(left<right){
int mid = (left+right)/2;
sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序,必须是left,mid,不可以是left,mid -1
sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
}
}
private static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i=left;
int j=mid+1;
int cur=0;
while(i<=mid && j<=right){
if(arr[i]<=arr[j]){
temp[cur++]=arr[i++];
}else{
temp[cur++]=arr[j++];
}
}
while(i<=mid){
temp[cur++]=arr[i++];
}
while(j<=right){
temp[cur++]=arr[j++];
}
t=0;
while(left<=right){
arr[left++]=temp[t++];
}
}
3.8 桶排序
桶式排序不再是一种基于比较的排序方法,它是一种比较巧妙的排序方式,但这种排序方式需要待排序的序列满足以下两个特征: 待排序列所有的值处于一个可枚举的范围之类; 待排序列所在的这个可枚举的范围不应该太大,否则排序开销太大。
基本思路:
- 设置固定数量的空桶。
- 把数据放到对应的桶中。
- 对每个不为空的桶中数据进行排序。
- 拼接从不为空的桶中数据,得到结果。
TOP K问题可以用桶排序来解决:
力扣地址:https://leetcode-cn.com/problems/top-k-frequent-elements/description/
解答:设置若干个桶,每个桶存储出现频率相同的数。桶的下标表示数出现的频率,即第 i 个桶中存储的数出现的频率为 i。把数都放到桶之后,从后向前遍历桶,最先得到的 k 个数就是出现频率最多的的 k 个数。
public int[] topKFrequent(int[] nums, int k) { Map<Integer,Integer> frequencyForNum=new HashMap<>(); for(int num:nums){ frequencyForNum.put(num,frequencyForNum.getOrDefault(num,0)+1); } List<Integer>[] buckets=new ArrayList[nums.length+1]; for(int key:frequencyForNum.keySet()){ int frequency=frequencyForNum.get(key); if(buckets[frequency]==null){ buckets[frequency]=new ArrayList<>(); } buckets[frequency].add(key); } List<Integer> topk=new ArrayList<>(); for(int i=buckets.length-1;i>=0 && topk.size()<k;i--){ if(buckets[i]==null){ continue; } if(buckets[i].size<=(k-topk.size())){ topk.addAll(buckets[i]); }else{ topk.addAll(buckets[i].subList(0,k-topk.size())); } } /** List转数组 */ int res=new int[k]; for (int i = 0; i < k; i++) { res[i] = topK.get(i); } return res; }
力扣地址:https://leetcode-cn.com/problems/sort-characters-by-frequency/description/
public String frequencySort(String s) { Map<Character,Integer> map=new HashMap<>(); for(char c:s.toCharArray()){ map.put(c,map.getOrDefault(c,0)+1); } List<Character>[] buckets=new ArrayList<>[]; for(char c:map.ketSet()){ int fre=map.get(c); if(buckets[fre]==null){ buckets[fre]=new ArrayList<>(); } buckets[fre].add(c); } StringBuilder str=new StringBuilder(); for(int i=buckets.length-1;i>=0;i--){ if(buckets[i]==null){ continue; } for(char c:buckets[i]){ for(int j=0;j<i;j++){ str.append(c); } } } return str.toString();}
四、动态规划
参考博客1:https://zhuanlan.zhihu.com/p/91582909
参考博客2:https://labuladong.gitbook.io/algo/mu-lu-ye-2
此篇博客动态规划章节第二小节内容地址:https://mp.weixin.qq.com/s/qvlfyKBiXVX7CCwWFR-XKg
4.1 斐波那契数列
4.1.1 爬楼梯
力扣地址:https://leetcode-cn.com/problems/climbing-stairs/description/
解答:解题步骤按照参考博客2,base case—>状态—选择—>确定dp数组。到达台阶N有两种方法,跳一个台阶过来或者跳两个台阶过来,所以列出状态转移方程:dp[i]=dp[i-1]+dp[i-2]
,采用状态压缩,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2],而不用一个数组
public int climbStairs(int n) {
if(n<=2) return n;
int[] dp=new int[n+1];
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
以下两种方法与线性代数关系密切:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Z63ya7h-1652019362989)(刷题笔记.assets/1631352283479.png)]
快速计算矩阵M的 n 次幂,就可以得到 f(n) 的值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LnIqHGYe-1652019362990)(刷题笔记.assets/1631352533941.png)]
4.1.2 强盗抢劫
力扣地址:https://leetcode-cn.com/problems/house-robber/description/
解答:定义dp 数组用来存储最大的抢劫量,其中 dp[i] 表示抢到第 i 个住户时的最大抢劫量。由于不能抢劫邻近住户,如果抢劫了第 i -1 个住户,那么就不能再抢劫第 i 个住户
public int rob(int[] nums) {
if(nums==null || nums.length==0){
return 0;
}
int n=nums.length;
if(n==1){
return nums[0];
}
int[] dp=new int[n];
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
for(int i=2;i<n;i++){
dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[n-1];
}
4.1.3 强盗在环形街区抢劫
力扣地址:https://leetcode-cn.com/problems/house-robber-ii/description/
解答:如何才能保证第一间房屋和最后一间房屋不同时偷窃呢?如果偷窃了第一间房屋,则不能偷窃最后一间房屋,因此偷窃房屋的范围是第一间房屋到最后第二间房屋;如果偷窃了最后一间房屋,则不能偷窃第一间房屋,因此偷窃房屋的范围是第二间房屋到最后一间房屋。
public int rob(int[] nums) {
if(nums==null || nums.length==0){
return 0;
}
if(nums.length==1)}{
return nums[0];
}
return Math.max{rob(nums,0,n-2),rob(nums,1,n-1)};
}
private int rob(int[] nums, int first, int last) {
int pre1=0;
int pre2=0;
int cur=0;
for(int i=first;i<=last;i++){
cur=Math.max(pre2+nums[i],pre1);
pre2=pre1;
pre1=cur;
}
return pre1;
}
public int rob(int[] nums) {
if(nums==null || nums.length==0){
return 0;
}
int n=nums.length;
if(n==1){
return nums[0];
}
return Math.max(rob(nums,0,nums.length-2),rob(nums,1,nums.length-1));
}
public int rob(int[] nums,int i,int j) {
if(i>j){
return 0;
}
if(i==j){
return nums[i];
}
int[] dp=new int[j-i+1];
dp[0]=nums[i];
dp[1]=Math.max(nums[i],nums[i+1]);
for(int k=2;k<j-i+1;k++){
dp[k]=Math.max(dp[k-1],dp[k-2]+nums[i+k]);
}
return dp[j-i];
}
4.1.4 信件错排
问题描述:有 N 个信件和信箱,每封信件对应一个正确信箱位置。现在它们被打乱,求错误装信方式的数量。保证每一封信都装在错误的位置。
解答:定义一个数组dp[]存储错误方式数量。dp[i]表示,有i封信、i个信箱情况下的错误装信方法总数。
参考博客:https://www.cnblogs.com/buptleida/p/13229545.html
private int MailMisalignment(int n){
if(n<2) return 0;
int[] dp=new int[n+1];
dp[1]=0;
dp[2]=1;
int[] dp = new int[n];
for(int i=3;i<=n;i++){
dp[i]=(i-1)*(dp[i-1]+dp[i-2]);
}
return dp[n];
}
4.1.5 母牛生产
题目描述:有一头母牛,它每年年初生一头小母牛。每头小母牛从第四个年头开始,每年年初也生一头小母牛。请编程实现在第n年的时候,共有多少头母牛?
public int cowNums(int n){
if(n<4) return n;
int []dp=new int [n+1];
dp[1]=1;
dp[2]=2;
dp[3]=3;
for(int i=4;i<=n;i++){
dp[i]=dp[i-1]+dp[i-3];
}
return dp[n];
}
4.2 矩阵路劲
4.2.1 矩阵的最小路径和
力扣地址:https://leetcode-cn.com/problems/minimum-path-sum/description/
解答:采用状态压缩,将二维降为一维
public int minPathSum(int[][] grid) {
int m=grid.length;
int n=grid[0].length;
int[][] dp=new int[m][n];
dp[0][0]=grid[0][0];
for(int i=1;i<m;i++){
dp[i][0]=dp[i-1][0]+grid[i][0];
}
for(int j=1;j<n;j++){
dp[0][j]=dp[0][j-1]+grid[0][j];
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[m-1][n-1];
}
4.2.2 矩阵的总路径数
力扣地址:https://leetcode-cn.com/problems/unique-paths/description/
解答:采用状态压缩,将二维降为一维
public int uniquePaths(int m, int n) {
int[][] dp=new int[m][n];
for(int i=0;i<m;i++){
dp[i][0]=1;
}
for(int i=0;i<n;i++){
dp[0][i]=1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
}
4.3 数组区间
4.3.1 数组区间和
力扣地址:https://leetcode-cn.com/problems/range-sum-query-immutable/description/
解答:最朴素的想法是存储数组 nums 的值,每次调用sumRange 时,通过循环的方法计算数组 nums 从下标 i到下标 j范围内的元素和,需要计算 j-i+1个元素的和。由于每次检索的时间和检索的下标范围有关,因此检索的时间复杂度较高,如果检索次数较多,则会超出时间限制。由于会进行多次检索,即多次调用 sumRange,因此为了降低检索的总时间,应该降低 sumRange 的时间复杂度,最理想的情况是时间复杂度 O(1)。为了将检索的时间复杂度降到 O(1),需要在初始化的时候进行预处理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qga38uL9-1652019362991)(C:\Users\pan\AppData\Roaming\Typora\typora-user-images\image-20210624203014391.png)]
class NumArray {
public int[] sums;
public NumArray(int[] nums){
sums=new int[nums.length+1];
for(int i=1;i<=nums.length;i++){
sums[i]=sums[i-1]+nums[i-1];
}
}
public int sumRange(int i,int j){
return sums[j+1]-sums[i];
}
}
4.3.2 数组中等差递增子区间的个数(不懂)
力扣地址:https://leetcode-cn.com/problems/arithmetic-slices/description/
public int numberOfArithmeticSlices(int[] A) {
int[] dp = new int[A.length];
int sum = 0;
for (int i = 2; i < dp.length; i++) {
if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {
dp[i] = 1 + dp[i - 1];
sum += dp[i];
}
}
return sum;
}
4.4 分割整数
4.4.1 分割整数的最大乘积
力扣地址:https://leetcode-cn.com/problems/integer-break/description/
解答:当 i≥2 时,假设对正整数 i 拆分出的第一个正整数是 j,则有以下两种方案:
-
将 i 拆分成 j 和i−j 的和,且i−j 不再拆分成多个正整数,此时的乘积是 j×(i−j);
-
将 i拆分成 j 和i−j 的和,且i−j 继续拆分成多个正整数,此时的乘积是j×dp[i−j]。
public int integerBreak(int n) {
int[] dp=new int[n+1];
for(int i=2;i<=n;i++){
int curMax=0;
for(int j=1;j<i;j++){
curMax=Math.max(curMax,Math.max(j*(i-j),dp[i-j]*j));
}
dp[i]=curMax;
}
return dp[n];
}
4.4.2 按平方数来分割整数
力扣地址:https://leetcode-cn.com/problems/perfect-squares/description/
解答:我只需将N拆为 i2,N-i2,然后采用动态规划。
public int numSquares(int n) {
int[] dp=new int[n+1];
for(int i=1;i<=n;i++){
int min=Integer.MAX_VALUE;
for(int j=1;j*j<=i;j++){
min=Math.min(dp[i-j*j],min);
}
dp[i]=min+1;
}
return dp[n];
}
4.4.3 分割整数构成字母字符串
力扣:https://leetcode-cn.com/problems/decode-ways/description/
解答:状态是长度为i的字符串可以解码的方式数量,能导致状态变化的选择主要分为两种:
- 对字符n单独解码
- 对字符n和n-1一起解码
细节:对basecase的处理,dp[0]=1,理解为空字符串可以有 1 种解码方法,解码出一个空字符串。
public int numDecodings(String s) {
int n=s.length;
int[] dp=new int[n+1];
dp[0]=1;
for(int i=1;i<=n;i++){
if(s.charAt(i-1)!='0'){
dp[i]+=dp[i-1];
}
if(s.charAt(i-2)!='0' && ((s.charAt(i-2)-'0')*10+(s.charAt(i-1)-'0')<=26)){
dp[i]+=dp[i-2];
}
}
return dp[n];
}
4.5 最长递增子序列
4.5.1 最长递增子序列
力扣地址:https://leetcode-cn.com/problems/longest-increasing-subsequence/description/
解答:定义 dp[i] 为考虑前 i 个元素,以第 i 个数字结尾的最长上升子序列的长度,nums[i] 必须被选取。我们从小到大计算dp 数组的值,在计算 dp[i] 之前,我们已经计算出 dp[0…i−1] 的值,则状态转移方程为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XOUIqMCe-1652019362992)(C:\Users\pan\AppData\Roaming\Typora\typora-user-images\image-20210626115942589.png)]
public int lengthOfLIS(int[] nums) {
if(nums==null || nums.length==0){
return 0;
}
int n=nums.length;
int[] dp=new int[n];
dp[0]=1;
int res=dp[0];
for(int i=1;i<n;i++){
int curMax=0;
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
curMax=Math.max(curMax,dp[j]);
}
}
dp[i]=curMax+1;
res=Math.max(dp[i],res);
}
return res;
}
4.5.2 一组整数对能够构成的最长链
力扣地址:https://leetcode-cn.com/problems/maximum-length-of-pair-chain/description/
解答:
public int findLongestChain(int[][] pairs) {
/* *必须对pairs按照第一个元素的大小先排序 */
Arrays.sort(pairs, (a, b) -> a[0] - b[0]);
if(pairs==null || pairs.length==0){
return 0;
}
int[] dp=new int[pairs.length];
Arrays.fill(dp,1);//这个步骤很必须,每个数对最少也能自己构成一个满足条件的链
int res=dp[0];
for(int i=1;i<pairs.length;i++){
for(int j=0;j<i;j++){
if(pairs[i][0]>pairs[j][1]){
dp[i]=Math.max(dp[i],dp[j]+1);
}
}
res=Math.max(res,dp[i]);
}
return res;
}
4.5.3 最长摆动子序列
力扣地址:https://leetcode-cn.com/problems/wiggle-subsequence/description/
解答:某个序列被称为上升摆动序列
,当且仅当该序列是摆动序列,且最后一个元素呈上升趋势。如序列 1,3,2,4
即为「上升摆动序列」。
某个序列被称为下降摆动序列
,当且仅当该序列是摆动序列,且最后一个元素呈下降趋势。如序列4,2,3,1
即为下降摆动序列
。
public int wiggleMaxLength(int[] nums) {
int n=nums.length;
if(n==1){
return n;
}
int[] up=new int[n];
int[] down=new int[n];
up[0]=1;
down[0]=1;
for(int i=1;i<n;i++){
if(nums[i]>nums[i-1]){
up[i]=Math.max(down[i-1]+1,up[i-1]);
down[i]=down[i-1];
}else if(nums[i]<nums[i-1]){
down[i]=Math.max(up[i-1]+1,down[i-1]);
up[i]=up[i-1];
}else{
up[i]=up[i-1];
down[i]=down[i-1];
}
}
return Math.max(up[n-1],down[n-1]);
}
4.6 最长公共子序列
力扣地址:https://leetcode-cn.com/problems/longest-common-subsequence/
解答:定义一个二维数组 dp 用来存储最长公共子序列的长度,其中dp[i][j]
表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1i 与 S2j 值是否相等,分为两种情况:
- 当 S1i==S2j 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1i 这个值,最长公共子序列长度加 1,即 dp[i][j] = dp[i-1][j-1] + 1。
- 当 S1i != S2j 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,或者 S1 的前 i个字符和 S2 的前 j-1 个字符最长公共子序列,取它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AwH7MK2B-1652019362992)(C:\Users\pan\AppData\Roaming\Typora\typora-user-images\image-20210629110722126.png)]
public int longestCommonSubsequence(String text1, String text2) {
int n1=text1.length;
int n2=text2.length;
int[][] dp=new int[n1+1][n2+1];
for(int i=1;i<=n1;i++){
for(int j=1;j<=n2;j++){
if(text1.charAt(i)==text2.charAt(j)){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[n1][n2];
}
4.7 0-1背包
有一个容量为 N 的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积 w 和价值 v。
定义一个二维数组 dp 存储最大价值,其中 dp[i][j]
表示前 i 件物品体积不超过 j 的情况下能达到的最大价值。设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论:
- 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,
dp[i][j]
=dp[i-1][j]
。 - 第 i 件物品添加到背包中,
dp[i][j]
=dp[i-1][j-w]
+ v。
第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。因此,0-1 背包的状态转移方程为:
// W 为背包总体积
// N 为物品数量
// weights 数组存储 N 个物品的重量
// values 数组存储 N 个物品的价值
public int knapsack(int W, int N, int[] weights, int[] values) {
int[][] dp=new int[N+1][W+1];
for(int i=1;i<=N;i++){
int weight=weights[i-1];
int value=values[i-1];
for(int j=1;j<=W;j++){
if(j>=weight){
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-w]+value);
}else{
dp[i][j]=dp[i-1][j];
}
}
}
return dp[N][W];
}
采用状态压缩后:
public int knapsack(int W, int N, int[] weights, int[] values) {
int[] dp=new int[W+1];
for(int i=1;i<=N;i++){
int weight=weights[i-1];
int value=values[i-1];
for(int j=1;j<=W;j++){
if(j>=weight){
dp[j]=Math.max(dp[j],dp[j-w]+value);
}
}
}
return dp[W];
}
4.7.1 划分数组为和相等的两部分
力扣地址:https://leetcode-cn.com/problems/partition-equal-subset-sum/description/
解答:背包大小为 sum/2 的 0-1 背包问题
public boolean canPartition(int[] nums) {
int n=nums.length;
if(n<2){
return false;
}
int maxSum=0;
int sum=0;
for(int tmp:nums){
sum+=tmp;
maxSum=Math.max(maxSum,tmp);
}
if(maxSum>sum/2){
return false;
}
if(sum%2!=0){
return false;
}
int n=nums.length;
boolean[] dp=new boolean[sum/2+1];
dp[0]=true;
for(int i=1;i<=n;i++){
int value=nums[i-1];
for(int j=sum/2;j>=value;--j){
dp[j]=dp[j] || dp[j-value];
}
}
return dp[sum/2];
}
4.7.2 改变一组数的正负号使得它们的和为一给定数
力扣:https://leetcode-cn.com/problems/target-sum/description/
解答:
public int findTargetSumWays(int[] nums, int S) {
int sum=0;
for(int tmp:nums){
sum+=tmp;
}
if(sum<S || (sum+S)%2==1){
return 0;
}
int target=(sum+S)/2;
int[] dp=new int[target+1];
dp[0]=1;
for(int data:nums){
for(int j=target;j>=data;--j){
dp[j]=d[j]+dp[j-data];
}
}
return dp[target];
}
4.7.3 01字符构成最多的字符串(没有思路)
力扣:https://leetcode-cn.com/problems/ones-and-zeroes/description/
解答:多维费用的 0-1 背包问题,有两个背包大小,0 的数量和 1 的数量。
public int findMaxForm(String[] strs, int m, int n) {
if(strs==null || strs.length==0){
return 0;
}
int[][] dp=new int[m+1][n+1];
for(String s:strs){
int zeros=0;
int ones=0;
for(char c:s){
if(c=='0'){
zeros++;
}else{
ones++;
}
}
for(int i=m;i>=zeros;i--){
for(int j=n;j>=ones;j--){
dp[i][j]=Math.max(dp[i][j],dp[i-zerso][j-ones]+1);
}
}
}
return dp[m][n];
}
4.7.4 找零钱的最少硬币数
力扣:https://leetcode-cn.com/problems/coin-change/description/
解答:因为硬币可以重复使用,完全背包问题,完全背包只需要将 0-1 背包的逆序遍历 dp 数组改为正序遍历即可。
public int coinChange(int[] coins, int amount) {
if(amount==0 || coins==null){
return 0;
}
int[] dp=new int[amount+1];
Arrays.fill(dp,amount+1);//此处填充amount+1的原因就是下面代码必须Math.min( )
dp[0]=0;
for(int i=1;i<=amount;i++){
for(int coin:coins){
if(i>=coin){
dp[i]=Math.min(dp[i],dp[i-coin]+1);
}
}
}
return dp[amount]>amount?-1:dp[amount];
}
4.7.5 找零钱的硬币数组合
力扣地址:https://leetcode-cn.com/problems/coin-change-2/description/
解答:完全背包问题,使用 dp 记录可达成目标的组合数目。
public int change(int amount, int[] coins) {
if(coins==null){
return 0;
}
int[] dp=new int[amount+1];
dp[0]=1;
for(int coin:coins){ //把硬币放在外循环
for(int i=coin;i<=amount;i++){
dp[i]+=dp[i-coin];
}
}
return dp[amount];
}
4.7.6 字符串按单词列表分割
力扣地址:https://leetcode-cn.com/problems/word-break/description/
解答:带顺序的完全背包问题,对物品的迭代应该放在最里层,对背包的迭代放在外层,只有这样才能让物品按一定顺序放入背包中。
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> set=new HashSet<>(wordDict);
int n=s.length();
boolean[] dp=new boolean[n+1];
dp[0]=true;
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++){
if(dp[j] && set.contains(s.substring(j,i))){
dp[i]=true;
break;
}
}
}
return dp[n];
}
4.7.7 组合总和
力扣:https://leetcode-cn.com/problems/combination-sum-iv/description/
解答:本题和4.7.5的区别就在于 本题的序列需要有序。是带顺序的完全背包问题
public int combinationSum4(int[] nums, int target) {
int[] dp=new int[target+1];
dp[0]=1;
for(int i=1;i<=target;i++){
for(int num:nums){
if(i>=num){
dp[i]+=dp[i-num];
}
}
}
return dp[target];
}
4.8 股票交易
4.8.1 需要冷却期的股票交易
力扣地址:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/
解答:首先这是一个二维动态规划问题,天数和状态为两个维度
我们目前持有一支股票,对应的「累计最大收益」记为 DP[i][0]
;
我们目前不持有任何股票,并且处于冷冻期中,对应的「累计最大收益」记为 DP[i][1]
;
我们目前不持有任何股票,并且不处于冷冻期中,对应的「累计最大收益」记为 DP[i][2]
。
public int maxProfit(int[] prices) {
if(prices==null || prices.length==0){
return 0;
}
int[][] dp=new int[n][3];
dp[0][0]=-prices[0];
for(int i=1;i<prices.length;i++){
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][2]-prices[i]);
dp[i][1]=dp[i][0]+prices[i];
dp[i][2]=Math.max(dp[i-1][1],dp[i-1][2]);
}
return Math.max(dp[n-1][1],dp[n-1][2]);
}
4.8.2 需要交易费用的股票
力扣地址:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/
解答:没有冷冻期,状态数为2了,而且关于费用,只需要关注卖出那一天就行
public int maxProfit(int[] prices, int fee) {
if(prices==null || prices.length==0){
return 0;
}
int n=prices.length;
int[][] dp=new int[n][2];
dp[0][0]=-prices[0];
for(int i=1;i<n;i++){
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-fee+prices[i]);
}
return dp[n-1][1];
}
4.8.3 只能进行两次的股票交易
力扣地址:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/description/
解答:这个题的区别还是在于他的状态上,由于我们最多可以完成两笔交易,因此在任意一天结束之后,我们会处于以下五个状态中的一种:
-
未进行过任何操作;
-
只进行过一次买操作;buy1
-
进行了一次买操作和一次卖操作,即完成了一笔交易;sell1
-
在完成了一笔交易的前提下,进行了第二次买操作;buy2
-
完成了全部两笔交易;sell2
public int maxProfit(int[] prices) {
if(prices==null || prices.length==0){
return 0;
}
int n=prices.length;
int buy1=-prices[0];
int sell1=0;
int buy2=-prices[0];
int sell2=0;
for(int i=1;i<n;i++){
buy1=Math.max(buy1,-prices[i]);
sell1=Math.max(sell1,buy1+prices[i]);
buy2=Math.max(buy2,sell1-prices[i]);
sell2=Math.max(sell2,buy2+prices[i]);
}
return sell2;
}
4.9 字符串编辑
4.9.1 删除两个字符串的字符使它们相等
力扣地址:https://leetcode-cn.com/problems/delete-operation-for-two-strings/description/
解答:思路转化,求两个字符串的最长公共子序列
public int minDistance(String word1, String word2) {
int n=word1.length();
int m=word2.length();
int[][] dp=new int[n+1][m+1];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j]);
}
}
}
return m+n-2*dp[n][m];
}
4.9.2 编辑距离
力扣地址:https://leetcode-cn.com/problems/edit-distance/description/
解答:
public int minDistance(String word1, String word2) {
int n=word1.length();
int m=word2.length();
int[][] dp=new int[n+1][m+1];
for(int i=1;i<=n;i++){
dp[i][0]=i;
}
for(int i=0;i<=m;i++){
dp[0][i]=i;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1];
}else{
dp[i][j]=Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1]))+1;
}
}
}
return dp[n][m];
}
4.9.3 复制粘贴字符
力扣地址:https://leetcode-cn.com/problems/2-keys-keyboard/description/
解答:转换为素数分解问题
对n进行分解质因数,应先找到一个最小的质数k(最小的质数就是2),然后按下述步骤完成:
-
(1)如果这个质数恰等于n,则说明分解质因数的过程已经结束,打印出即可。
-
(2)如果n!=k,但n能被k整除,则应打印出k的值,并用n除以k的商,作为新的正整数n,重复执行第一步。
-
(3)如果n不能被k整除,则用k+1作为k的值,重复执行第一步。
public int minSteps(int n) {
int ans=0;
int k=2;
while(n>1){
while(n%k==0){
ans+=k;
n/=k;
}
k++;
}
return ans;
}
4.10 数对之差的最大值
在数组中,数字减去它右边的数字得到一个数对之差。求所有数对之差的最大值。例如在数组{2, 4, 1, 16, 7, 5, 11, 9}中,数对之差的最大值是11,是16减去5的结果。
解答:如果输入一个长度为n的数组numbers,我们先构建一个长度为n-1的辅助数组sub,并且sub等于numbers[i]-numbers[i+1](0<=i<n-1)。原始数组中最大的数对之差(即numbers[i] – numbers[j + 1])其实是辅助数组sub中最大的连续子数组之和。
public static int getMaxSub(int[] arr){
int n=arr.length-1;
int[] sub=new int[n-1];
for(int i=0;i<n-1;i++){
sub[i]=arr[i]-arr[i+1];
}
int[] dp=new int[n-1];
dp[0]=sub[0];
int res=Integer.MIN_VALUE;
for(int i=1;i<n-1;i++){
dp[i]=Math.max(dp[i-1]+sub[i],sub[i]);
res=Math.max(res,dp[i]);
}
return res;
}
五、树
5.1 递归
5.1.1 树的高度
力扣地址:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/description/
解答:
public int maxDepth(TreeNode root) {
if(root==null){
return 0;
}
return 1+Math.max(maxDepth(root.left),maxDepth(root.right));
}
5.1.2 平衡树
力扣地址:https://leetcode-cn.com/problems/balanced-binary-tree/description/
解答:
public boolean isBalanced(TreeNode root) {
if(root==null) return true;
int l=maxDepth(root.left);
int r=maxDepth(root.right);
return Math.abs(l-r)<=1 && (isBalanced(root.left)&&isBalanced(root.right));
}
private int maxDepth(TreeNode root) {
if(root==null) return 0;
return 1+Math.max(maxDepth(root.left),maxDepth(root.right));
}
5.1.3 两节点的最长路径
力扣地址:https://leetcode-cn.com/problems/diameter-of-binary-tree/description/
int result=0;
public int diameterOfBinaryTree(TreeNode root) {
maxDepth(root);
return result;
}
private int maxDepth(TreeNode root) {
if(root==null){
return 0;
}
int leftLen=maxDepth(root.left);
int rightLen=maxDepth(root.right);
result=Math.max(result,leftLen+rightLen);
return 1+Math.max(leftLen,rightLen);
}
5.1.4 翻转树
力扣地址:https://leetcode-cn.com/problems/invert-binary-tree/description/
public TreeNode invertTree(TreeNode root) {
if(root==null){
return null;
}
TreeNode right=root.right;
root.right=invertTree(root.left);
root.left=invertTree(right);
return root;
}
5.1.5 归并两棵树
力扣地址:https://leetcode-cn.com/problems/merge-two-binary-trees/description/
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1==null || root2==null){
return root1==null?root2:root1;
}
TreeNode root=new TreeNode(root1.val+root2.val);
root.left=mergeTrees(root1.left,root2.left);
root.right=mergeTrees(root1.right,root2.right);
return root;
}
5.1.6 判断路径和是否等于一个数
力扣地址:https://leetcode-cn.com/problems/path-sum/description/
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null){
return false;
}
if(root.left==null && root.right==null && tatgetSum==root.val){
return true;
}
return hasPathSum(root.left,targetSum-root.val) || hasPathSum(root.right,targetSum-root.val);
}
5.1.7 统计路径和等于一个数的路径数量
力扣地址:https://leetcode-cn.com/problems/path-sum-iii/description/
解答:
int result=0;
public int pathSum(TreeNode root, int sum) {
if(root==null){
return 0;
}
hasPathSum(root,sum);
pathSum(root.left,sum);
pathSum(root.right,sum);
return result;
}
public void hasPathSum(TreeNode root, int sum) {
if(root==null){
return;
}
if(root.val==sum){
result++;
}
hasPathSum(root.left,sum-root.val);
hasPathSum(root.right,sum-root.val);
}
5.1.8 子树
力扣地址:https://leetcode-cn.com/problems/subtree-of-another-tree/description/
解答:
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(root==null || subRoot==null){
return root==null?false:true;
}
return isEqual(root,subRoot) || isSubtree(root.left,subRoot) || isSubtree(root.right,subRoot);
}
public boolean isEqual(TreeNode root1,TreeNode root2){
if(root1==null && root2==null){
return true;
}
if(root1==null || root2==null){
return false;
}
if(root1.val!=root2.val){
return false;
}else{
return isEqual(root1.left,root2.left) && isEqual(root1.right,root2.right);
}
}
5.1.9 树的对称
力扣地址:https://leetcode-cn.com/problems/symmetric-tree/description/
解答:
public boolean isSymmetric(TreeNode root) {
if(root==null){
return false;
}
return compare(root.left,root.right);
}
private boolean compare(TreeNode root1,TreeNode root2){
if(root1==null && root2==null) return true;
if(root1==null || root2==null) return false;
return root1.val==root2.val && compare(root1.left,root2.right)&& compare(root1.right,root2.left);
}
5.1.10 最小路径
力扣地址:https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/description/
解答:
public int minDepth(TreeNode root) {
if(root==null) return 0;
if(root.left==null || root.right==null){
return 1+minDepth(root.left)+minDepth(root.right);
}
return 1+Math.min(minDepth(root.left),minDepth(root.right));
}
5.1.11 统计左叶子节点的和
力扣:https://leetcode-cn.com/problems/sum-of-left-leaves/description/
解答:
public int sumOfLeftLeaves(TreeNode root) {
if(root==null) return 0;
if(isLeaf(root.left)) return root.left.val+sumOfLeftLeaves(root.right);
return sumOfLeftLeaves(root.left)+sumOfLeftLeaves(root.right);
}
private boolean isLeaf(TreeNode node){
if(node==null) return false;
return node.left==null && node.right==null
}
5.1.12 相同节点值的最大路径长度
力扣地址:https://leetcode-cn.com/problems/longest-univalue-path/
解答:
private int path=0;
public int longestUnivaluePath(TreeNode root) {
dfs(root);
return path;
}
private int dfs(TreeNode root){
if(root==null){
return 0;
}
int left=dfs(root.left);
int right=dfs(root.right);
int leftPath=root.left!=null && root.left.val==root.val?left+1:0;
int rightPath=root.right!=null && root.right.val==root.val?right+1:0;
path=Math.max(path,leftPath+rightPath);
return Math.max(rightPath,leftPath);
}
5.1.13 间隔遍历
力扣地址:https://leetcode-cn.com/problems/house-robber-iii/description/
解答:
Map<TreeNode,Integer> cache=new HashMap<>();
public int rob(TreeNode root) {
if(root==null) return 0;
if(cache.containsKey(root)){
return cache.get(root);
}
int robRoot=root.val;
if(root.left!=null){
robRoot+=rob(root.left.left)+rob(root.left.right);
}
if(root.right!=null){
robRoot+=rob(root.right.left)+rob(root.right.right);
}
int res=Math.max(robRoot,rob(root.left)+rob(root.right));
cache.put(root,res);
return res;
}
5.1.14 找出二叉树中第二小的节点
力扣地址:https://leetcode-cn.com/problems/second-minimum-node-in-a-binary-tree/description/
解答:思路:一个结点如果存在子树,左右子树要么值相等,要么不相等
不相等:在当前树中,较小的值和节点的值一样,第二小的必然是更大的那个。所以我们将返回结果res设置为该较大的值,因为较大的值在他的子树中一定是最小的值了,所以对较小值的树进行递归查找,是否会有更小的第二小的值(此处有点绕需要理解)
若相等:分别递归左右子树
int res=-1;
public int findSecondMinimumValue(TreeNode root) {
if(root==null || root.left==null){
return -1;
}
if(root.left.val!=root.right.val){
int bigger=root.left.val>root.right.val?root.left.val:root.right.val;
res=res==-1?bigger:Math.min(res,bigger);
findSecondMinimumValue(root.left.val>root.right.val?root.right:root.left);
}else{
findSecondMinimumValue(root.left);
findSecondMinimumValue(root.right);
}
return res;
}
5.2 层次遍历
使用 BFS 进行层次遍历。不需要使用两个队列来分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。
5.2.1 一棵树每层节点的平均
力扣地址:https://leetcode-cn.com/problems/average-of-levels-in-binary-tree/description/
public List<Double> averageOfLevels(TreeNode root) {
List<Double> list=new ArrayList<>();
if(root==null) return list;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
int count=queue.size();
double sum=0;
for(int i=0;i<count;i++){
TreeNode node=queue.poll();
sum+=node.val;
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
list.add(sum/count);
}
return list;
}
5.2.2 得到左下角的节点
力扣地址:https://leetcode-cn.com/problems/find-bottom-left-tree-value/description/
解答:
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
while(!queue.isEmpty){
TreeNode node=queue.poll();
if(node.right!=null) queue.add(node.right);
if(node.left!=null) queue.add(node.left);
}
return node.val;
}
5.3 前中后序遍历
1
/ \
2 3
/ \ \
4 5 6
- 层次遍历顺序:[1 2 3 4 5 6]
- 前序遍历顺序:[1 2 4 5 3 6]
- 中序遍历顺序:[4 2 5 1 3 6]
- 后序遍历顺序:[4 5 2 6 3 1]
层次遍历使用 BFS 实现,利用的就是 BFS 一层一层遍历的特性;而前序、中序、后序遍历利用了 DFS 实现。前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。
5.3.1 非递归实现二叉树的前序遍历
力扣地址:https://leetcode-cn.com/problems/binary-tree-preorder-traversal/description/
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list=new ArrayList<>();
Stack<TreeNode> stack=new Stack<>();
if(root==null) return list;
stack.push(root);
while(!stack.isEmpty()){
TreeNode node=stack.pop();
list.add(node.val);
if(node.right!=null){
stack.push(node.right);
}
if(node.left!=null){
stack.push(node.left);
}
}
return list;
}
5.3.2 非递归实现二叉树的后序遍历
力扣地址:https://leetcode-cn.com/problems/binary-tree-postorder-traversal/description/
解答:
List<Integer> list=new ArrayList<>();
public List<Integer> postorderTraversal(TreeNode root) {
if(root==null) return list;
postorderTraversal(root.left);
postorderTraversal(root.right);
list.add(root.val);
return list;
}
public List<Integer> postorderTraversal(TreeNode root) {
Stack<Integer> stack=new Stack();
List<Integer> list=new ArrayList<>();
if(root==null) return list;
stack.push(root);
while(!stack.isEmpty()){
TreeNode node=stack.pop();
if(node.left!=null){
stack.push(node.left);
}
if(node.right!=null){
stack.push(node.right);
}
list.add(node.val);
}
Collections.reverse(list);
return list;
}
5.3.3 非递归实现二叉树的中序遍历
力扣地址:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/description/
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> stack=new Stack<>();
List<Integer> list=new ArrayList<>();
while(root!=null || !stack.isEmpty){
while(root!=null){
stack.push(root);
root=root.left;
}
TreeNode node=stack.pop();
list.add(node.val);
root=node.right;
}
return list;
}
5.4 二叉查找树
力扣地址:https://leetcode-cn.com/problems/trim-a-binary-search-tree/description/
二叉查找树根肯定是大于左树,小于右树,所以这个题就是递归。
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root==null) return null;
if(root.val<low) return trimBST(root.right,low,high);
if(root.val>high) return trimBST(root.left,low,high);
root.left=trimBST(root.left,low,high);
root.right=trimBST(root.right,low,high);
return root;
}
5.4.1 修剪二叉树
力扣地址:https://leetcode-cn.com/problems/trim-a-binary-search-tree/description/
解答:
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root==null) return null;
if(root.val<low) return trimBST(root.right,low,high);
if(root.val>high) return trimBST(root.left,low,high);
root.left=trimBST(root.left,low,high);
root.right=trimBST(root.right,low,high);
return root;
}
5.4.2 寻找二叉查找树的第 k 个元素
力扣地址:https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/description/
解答:
中序遍历
public int kthSmallest(TreeNode root, int k) {
List<Integer> list=new ArrayList<>();
middle(root,list);
return list.get(k-1);
}
private void middle(TreeNode root,List<Integer> list){
if(root==null) return;
middle(root.left,list);
list.add(root.val);
middle(root.right,list);
}
递归
public int kthSmallest(TreeNode root, int k) {
int leftCount=count(root.left);
if(leftCount==k-1) return root.val;
if(leftCount>k-1) return kthSmallest(root.left,k);
return kthSmallest(root.right,k-1-elftCount);
}
private int count(TreeNode node) {
if(root==null) return null;
return 1+count(root.left)+count(root.right);
}
5.4.3 把二叉查找树每个节点的值都加上比它大的节点的值
力扣地址:https://leetcode-cn.com/problems/convert-bst-to-greater-tree/description/
int sum=0;
public TreeNode convertBST(TreeNode root) {
if(root==null) return null;
convertBST(root.right);
sum+=root.val;
root.val=sum;
convertBST(root.left);
return root;
}
5.4.4 二叉查找树的最近公共祖先
力扣地址:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/
解答:
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null) return null;
if(root.val>Math.max(p.val,q.val)){
return lowestCommonAncestor(root.left,p,q);
}
if(root.val<Math.min(p.val,q.val)){
return lowestCommonAncestor(root.right,p,q);
}
return root;
}
5.4.5 二叉树的最近公共祖先
力扣地址:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/description/
解答:
TreeNode res=null;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null) return null;
isContain(root,p,q);
return res;
}
private boolean isContain(TreeNode root,TreeNode p,TreeNode q){
if(root==null) return false;
boolean lSon=isContain(root.left,p,q);
boolean rSon=isContain(root.right,p,q);
if((lSon && rSon) || ((root.val==p.val || root.val==q.val) && (lSon || rSon))){
res=root;
}
return lSon || rSon || (root.val==p.val || root.val==q.val);
}
5.4.6 从有序数组中构造二叉查找树
力扣地址:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/description/
解答:
public TreeNode sortedArrayToBST(int[] nums) {
return BuildTree(nums,0,nums.length-1);
}
private TreeNode BuildTree(int[] nums, int start, int end){
if(start>end){
return null;
}
int index=(start+end)/2;
TreeNode node=new TreeNode(nums[index]);
node.left=BuildTree(nums,start,index-1);
node.right=BuildTree(nums,index-1,end);
return node;
}
5.4.7 根据有序链表构造平衡的二叉查找树
力扣地址:https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree/description/
解答:
public TreeNode sortedListToBST(ListNode head) {
if(head==null) return null;
if(head.next==null) return new TreeNode(head.val);
ListNode preMid=preMid(head);
ListNode mid=preMid.next;
preMid.next=null;
TreeNode root=new TreeNOde(mid.val);
root.left=sortedListToBST(head);
root.right=sortedListToBST(mid.next);
return root;
}
/**
* 快慢指针
*/
private ListNode preMid(ListNode head) {
ListNode slow=head;
ListNode fast=head.next;
ListNode pre=head;
while(fast!=null && fast.next!=null){
pre=slow;
solw=slow.next;
fast=fast.next.next;
}
return pre;
}
5.4.8 在二叉查找树中寻找两个节点,使它们的和为一个给定值
力扣地址:https://leetcode-cn.com/problems/two-sum-iv-input-is-a-bst/description/
public boolean findTarget(TreeNode root, int k) {
Set<Integer> set=new HashSet<>();
return find(root,k,set);
}
private boolean find(TreeNode root,int k,Set<Integer> set){
if(root==null) return false;
if(set.contains(k-root.val)) return true;
set.add(root.val);
return find(root.left,k,set) || find(root.right,k,set);
}
5.4.9 在二叉查找树中查找两个节点之差的最小绝对值
力扣地址:https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst/description/
解答:
int res=Integer.MAX_VALUE;
int pre=-1;
public int getMinimumDifference(TreeNode root) {
inoder(root);
return res;
}
private void inoder(TreeNode root){
if(root==null) return;
inoder(root.left);
if(pre!=-1) res=Math.min(res,root.val-pre);
pre=root.val;
inoder(root.right);
}
5.4.10 寻找二叉查找树中出现次数最多的值
力扣地址:https://leetcode-cn.com/problems/find-mode-in-binary-search-tree/description/
解答:
我们可以顺序扫描中序遍历序列,用 base 记录当前的数字,用count 记录当前数字重复的次数,用maxCount 来维护已经扫描过的数当中出现最多的那个数字的出现次数,用answer 数组记录出现的众数。
List<Integer> answer=new ArrayList<>();
int base=0,count,maxCount;
public int[] findMode(TreeNode root) {
if(root==null) return null;
midOrder(root);
int[] mode = new int[answer.size()];
for (int i = 0; i < answer.size(); ++i) {
mode[i] = answer.get(i);
}
return mode;
}
private void midOrder(TreeNode root){
if(root==null) return;
midOrder(root.left);
update(root.val);
midOrder(root.right);
}
private void update(int x){
if(x==base){
count++;
}else{
base=x;
count=1;
}
if(maxCount==count){
answer.add(base);
}else if(maxCount<count){
maxCount=count;
answer.clear();
answer.add(base);
}
}
5.5 前缀树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ZhOJXVL-1652019362993)(C:\Users\pan\AppData\Roaming\Typora\typora-user-images\image-20210712203900117.png)]
前缀树:用于判断字符串是否存在或者是否具有某种字符串前缀。
5.5.1 实现一个前缀树
力扣地址:https://leetcode-cn.com/problems/implement-trie-prefix-tree/description/
解答:
public class Node(){
Node[] next=new Node[26];
boolean isLeaf;
}
private Node root;
/** Initialize your data structure here. */
public Trie() {
root=new Node();
}
/** Inserts a word into the trie. */
public void insert(String word) {
insert(word,root);
}
public void insert(String word,Node node){
if(word==null) return;
if(word.length==0){
node.isLeaf=true;
return;
}
int index=getIndexForNext(word.charAt(0));
if(node.next[index]==null){
node.next[index]=new Node();
}
insert(word.subString(1),node.next[index]);
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
return search(root,word);
}
private boolean search(Node node,String word){
if(node==null) return false;
if(word.length()==0) return node.isLeaf;
int index=getIndexForNext(word.charAt(0));
return search(node.next[index],word.subString(1));
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
return startWith(root,prefix);
}
public boolean startWith(Node node,String prefix){
if(node==null) return false;
if(prefix.length()==0) return true;
int index=getIndexForNext(word.charAt(0));
return startWith(node.next[index],prefix.subString(1));
}
private int getIndexForNext(char char){
return char-'a';
}
5.5.2 实现一个 前缀树,用来求前缀和
力扣地址:https://leetcode-cn.com/problems/map-sum-pairs/description/
解答:
private class Node{
Node[] child=new Node[26];
int value;
}
private Node root;
/** Initialize your data structure here. */
public MapSum() {
root=new Node();
}
public void insert(String key, int val) {
insert(root,key,val);
}
private void insert(Node node,String key,int val){
if(node==null) return;
if(key==0){
node.value=val;
return;
}
int index=getIndexForNext(key.charAt(0));
if(node.next[index]==null){
node.next[index]==new node();
}
insert(node.next[index],key.subString[1],int val);
}
public int sum(String prefix) {
}
public int sum(Node node,String prefix){
if(node == null) return 0;
if(prefix.length()!=0){
int index=getIndexForNext(prefix.charAt(0));
sum(node.next[idnex],prefix.subString(1));
}
int sum=node.val;
for(Node child:next){
sum+=sum(child,String prefix);
}
return sum;
}
private int getIndexForNext(char char){
return char-'a';
}
六 栈和队列
6.1 用栈实现队列
力扣地址: https://leetcode-cn.com/problems/implement-queue-using-stacks/description/
解答:
private Stack<Integer> in;
private Stack<Integer> out;
/** Initialize your data structure here. */
public MyQueue() {
in=new Stack<>();
out=new Stack<>();
}
/** Push element x to the back of queue. */
public void push(int x) {
in.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
in2out();
return out.pop();
}
/** Get the front element. */
public int peek() {
in2out();
return out.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return in.isEmpty() && out.isEmpty();
}
private void in2out(){
if(out.isEmpty()){
while(!in.isEmpty()){
out.push(in.pop());
}
}
}
6.2 用队列实现栈
力扣地址:https://leetcode-cn.com/problems/implement-stack-using-queues/description/
解答:在将一个元素 x 插入队列时,为了维护原来的后进先出顺序,需要让 x 插入队列首部。而队列的默认插入顺序是队列尾部,因此在将 x 插入队列尾部之后,需要让除了 x 之外的所有元素出队列,再入队列。
private Queue<Integer> queue;
/** Initialize your data structure here. */
public MyStack() {
queue=new LinkedList<>();
}
/** Push element x onto stack. */
public void push(int x) {
queue.add(x);
int count=queue.size();
while(count-->1){
queue.add(queue.poll());
}
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
return queue.remove();
}
/** Get the top element. */
public int top() {
return queue.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return queue.isEmpty();
}
6.3 最小值栈
力扣地址:https://leetcode-cn.com/problems/min-stack/description/
Stack<Integer> dataStack;
Stack<Integer> minStack;
int min;
/** initialize your data structure here. */
public MinStack() {
dataStack=new Stack<>();
minStack=new Stack<>();
min=Integer.MAX_VALUE;
}
public void push(int val) {
min=Math.min(min,val);
dataStack.push(val);
minStack.push(min);
}
public void pop() {
dataStack.pop();
minStack.pop();
min=minStack.isEmpty()?Integer.MAX_VALUE:minStack.peek();
}
public int top() {
return dataStack.peek();
}
public int getMin() {
return min;
}
6.4 数组中元素与下一个比它大的元素之间的距离
力扣地址:https://leetcode-cn.com/problems/daily-temperatures/description/
解答:这里涉及到了单调栈
的概念,参考博客:https://www.cxyxiaowu.com/450.html
public int[] dailyTemperatures(int[] temperatures) {
int len=temperatures.length;
Stack<Integer> stack=new Stack<>();
int[] result=new int[len];
for(int i=0;i<len;i++){
while(!stack.isEmpty() && temperatures[i]>temperatures[stack.peek()]){
int index=stack.pop();
result[index]=i-index;
}
stack.push(i);
}
return result;
}
6.5 循环数组中比当前元素大的下一个元素
力扣地址:https://leetcode-cn.com/problems/next-greater-element-ii/description/
解答:依旧采用单调栈来处理
public int[] nextGreaterElements(int[] nums) {
int n=nums.length;
Stack<Integer> stack=new Stack<>();
int[] ans=new int[n];
Arrays.fill(ans,-1);
for(int i=0;i<2*n;i++) {
int realIndex=i % n;
while(!stack.isEmpty() && nums[realIndex]>nums[stack.peek()]){
int popIndex=stack.pop();
ans[popIndex]=nums[realIndex];
}
if(ans[realIndex]==-1){
stack.push(realIndex);
}
}
return ans;
}
6.6 逆序栈
一个栈依次压入1、2、3、4、5,那么从栈顶到栈底分别为5、4、3、2、1。将这个栈转置后,从栈顶到栈底为1、2、3、4、5,也就是实现栈中元素的逆序,但是只能运用递归函数来实现,不能使用其他数据结构
//要通过递归来实现压入5、4、3、2、1,那么递归中获得这些数据的顺序应该是1、2、3、4、5,也就是需要依次获得原栈的栈底元素。而获得栈底元素也可以通过另一个递归来实现。于是我们可以通过两个递归函数来实现整个过程
public class Solution {
public int getLast(Stack<Integer> s) {
int cur=s.pop();//得到最后一个元素,并且最后一个元素删除
if(s.isEmpty()){
return cur;
}else{
int last=getLast(s);
s.push(cur);
return last;
}
}
public void reverse(Stack<Integer> s) {
if(s==null || s.isEmpty()){
return;
}
int last=getLast(s);
reverse(s);
s.push(last);
}
}
6.7 计算表达式
地址:leetcode108
public int calculate(String s) {
Deque<Integer> stack = new LinkedList<Integer>();
char preSign = '+';
int num = 0;
int n = s.length();
for (int i = 0; i < n; ++i) {
if (Character.isDigit(s.charAt(i))) {
num = num * 10 + s.charAt(i) - '0';
}
if (!Character.isDigit(s.charAt(i)) && s.charAt(i) != ' ' || i == n - 1) {
switch (preSign) {
case '+':
stack.push(num);
break;
case '-':
stack.push(-num);
break;
case '*':
stack.push(stack.pop() * num);
break;
default:
stack.push(stack.pop() / num);
}
preSign = s.charAt(i);
num = 0;
}
}
int ans = 0;
while (!stack.isEmpty()) {
ans += stack.pop();
}
return ans;
}
中缀表达式转后缀表达式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6MMhmTlA-1652019362993)(刷题笔记.assets/1632571568927.png)]
后缀表达式计算方法
后缀表达式的计算就是从左到右扫描表达式,遇到数字就将其压入栈,遇到操作符表示可以计算,这时取出栈顶的两个元素进行操作,然后再次将结果压入栈,最后栈里会留下一个元素
七 哈希表
7.1 数组中两个数的和为给定值(两数之和)
力扣地址:https://leetcode-cn.com/problems/two-sum/description/
解答:用Hash缓存值和对应的数组下标
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> hash=new HashMap<>();
int n=nums.length;
for(int i=0;i<n;i++){
if(!hash.containsKey(target-nums[i])){
hash.put(nums[i],i);
}else {
return new int[]{i,hash.get(target-nums[i])};
}
}
return null;
}
7.2 判断数组中是否含有重复元素
力扣地址:https://leetcode-cn.com/problems/contains-duplicate/description/
解答:
public boolean containsDuplicate(int[] nums) {
Set<Integer> set=new HashSet<>();
for (int temp:nums
) {
if(set.contains(temp)){
return true;
}else{
set.add(temp);
}
}
return false;
}
7.3 最长和谐序列
力扣地址:https://leetcode-cn.com/problems/longest-harmonious-subsequence/description/
public int findLHS(int[] nums) {
Map<Integer,Integer> map=new HashMap<>();
for(int temp:nums){
map.put(temp,map.getorD)
}
}
7.4 最长连续序列
力扣地址:https://leetcode-cn.com/problems/longest-consecutive-sequence/description/
解答:
public int longestConsecutive(int[] nums) {
Set<Integer> set=new HashSet<>();
for(int temp:nums){
set.add(temp);
}
int res=0;
for(int data:nums){
if(!set.contains(data-1)){
int templen=1;
int curdata=data;
while(set.contains(curdata+1)){
templen++;
curdata++;
}
res=Math.max(res,templen);
}
}
return res;
}
7.5 有效的括号
力扣地址:https://leetcode-cn.com/problems/valid-parentheses/
public boolean isValid(String s) {
Map<Character,Character> map=new HashMap<>();
map.put(')','(');
map.put(']','[');
map.put('}','{');
Stack<Character> stack=new Stack<>();
for(char c:s.toCharArray()){
if(map.containsKey(c)){
if(stack.isEmpty() || stack.peek()!=map.get(c)){
return false;
}else{
stack.pop();
}
}else{
stack.push(c);
}
}
return stack.isEmpty();
}
八 贪心思想
8.1 分配饼干
力扣地址:https://leetcode-cn.com/problems/assign-cookies/description/
解答:
public int findContentChildren(int[] g, int[] s) {
int glen=g.length;
int slen=s.length;
Arrays.sort(g);
Arrays.sort(s);
int gi=0;
int si=0;
int res=0;
while(gi<glen && si<slen){
if(g[gi]<=s[si]){
res++;
gi++;
si++;
}else{
si++;
}
}
return res;
}
8.2 不重叠的区间个数
力扣地址:https://leetcode-cn.com/problems/non-overlapping-intervals/description/
解答:此题也可以用动态规划来解。以下为贪心算法
public int eraseOverlapIntervals(int[][] intervals) {
int n=intervals.length;
Arrays.sort(intervals,Comparator.comparingInt(o->o[1]));
int res=1;
int end=intervals[0][1];
for(int i=1;i<n;i++){
if(intervals[i][0]>=end){
res++;
end=intervals[i][1];
}
}
return n-res;
}
8.3 投飞镖刺破气球
力扣地址:https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/description/
解答:
public int findMinArrowShots(int[][] points) {
int n=points.length;
Arrays.sort(points,Comparator.comparingInt(o->o[1]));
int res=0;
int end=points[0][1];
for(int i=1;i<n;i++){
if(points[i][0]>end){
res++;
end=points[i][1];
}
}
return res+1;
}
8.4 买卖股票最大的收益
力扣地址:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/description/
解答:
思路就是维持一个最小值,假设总是在最小值处买入,然后遍历每个点,求差值去更新res。
public int maxProfit(int[] prices) {
int n=prices.length;
int res=0;
int sofarMin=prices[0];
for(int i=1;i<n;i++){
if(sofarMin>prices[i]){
sofarMin=prices[i];
}else{
res=Math.max(res,prices[i]-sofarMin);
}
}
return res;
}
8.5 买卖股票的最大收益 II
力扣地址:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/description/
解答:
这个题的思路就,如果今天和昨天相比能挣钱,就立马这份钱挣了。
public int maxProfit(int[] prices) {
int n=prices.length;
int in=prices[0];
int res=0;
for(int i=1;i<n;i++){
if(prices[i]>in){
res+=prices[i]-in;
}
in=prices[i];
}
return res;
}
8.6 种植花朵
力扣地址:https://leetcode-cn.com/problems/can-place-flowers/description/
解答:从左向右遍历花坛,在可以种花的地方就种一朵。
这里可以种花的条件是:
- 自己为空
- 左边为空 或者 自己是最左
- 右边为空 或者 自己是最右
最后判断n朵花是否有剩余
public boolean canPlaceFlowers(int[] flowerbed, int n) {
int len= flowerbed.length;
for(int i=0;i<len;i++){
if(flowerbed[i]==0 && (i==0 || flowerbed[i-1]==0) && (i==len-1 || flowerbed[i+1]==0)){
n--;
flowerbed[i]=1;
}
if(n<=0) return true;
}
return n<=0;
}
8.7 判断是否为子序列
力扣地址:https://leetcode-cn.com/problems/is-subsequence/description/
解答:类似于分配饼干的题目
public boolean isSubsequence(String s, String t) {
int sl=s.length();
int tl=t.length();
int sindex=0;
int tindex=0;
int num=0;
while(sindex<sl && tindex<tl){
if(s.charAt(sindex)==t.charAt(tindex)){
sindex++;
tindex++;
num++;
}else{
tindex++;
}
}
return num==s.length();
}
8.8 修改一个数成为非递减数组
力扣地址:https://leetcode-cn.com/problems/non-decreasing-array/description/
解答:本题是要维持一个非递减的数列,所以遇到递减的情况时(nums[i] > nums[i + 1]),要么将前面的元素缩小,要么将后面的元素放大。但是本题唯一的易错点就在这,
- 如果将nums[i]缩小,可能会导致其无法融入前面已经遍历过的非递减子数列
- 如果将nums[i + 1]放大,可能会导致其后续的继续出现递减
在遍历时,每次需要看连续的三个元素,也就是瞻前顾后,遵循以下两个原则:需要尽可能不放大nums[i + 1],这样会让后续非递减更困难;如果缩小nums[i],但不破坏前面的子序列的非递减性;
public boolean checkPossibility(int[] nums) {
int n=nums.length;
int count=0;
for(int i=1;i<n && count<2;i++){
if(nums[i]>=nums[i-1]){
continue;
}
count++;
if(i-2>=0 && nums[i]<nums[i-2]){
nums[i]=nums[i-1];
}else{
nums[i-1]=nums[i];
}
}
return count<2;
}
8.9 子数组最大的和
力扣地址:https://leetcode-cn.com/problems/maximum-subarray/description/
解答:这道题虽然归类为贪心算法,但是一直没想出该如何使用贪心策略,以下为动态规划解法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gi6vVimI-1652019362994)(C:\Users\pan\Desktop\笔记\notes\刷题笔记.assets\image-20210717193850824.png)]
public int maxSubArray(int[] nums) {
int len=nums.length;
int[] dp=new int[len];//dp[i]表示以第i个数结尾的连续子数组的最大和
dp[0]=nums[0];
int res=nums[0];
for(int i=1;i<len;i++){
dp[i]=Math.max(nums[i],dp[i-1]+nums[i]);
res=Math.max(res,dp[i]);
}
return res;
}
8.9.1 子数组最大乘积
考虑当前位置如果是一个负数的话,那么我们希望它前一个位置结尾的某个段的积也是个负数,这样就可以负负得正,并且我们希望这个积尽可能「负得更多」,即尽可能小。如果当前位置是一个正数的话,我们更希望以它前一个位置结尾的某个段的积也是个正数,并且希望它尽可能地大。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gXcVpUQY-1652019362994)(C:\Users\pan\Desktop\笔记\notes\刷题笔记.assets\image-20210815120644338.png)]
public int maxProduct(int[] nums) {
int[] max=new int[nums.length];
int[] min=new int[nums.length];
max[0]=nums[0];
min[0]=nums[0];
for(int i=1;i<nums.length;i++){
max[i]=Math.max(nums[i],Math.max(nums[i]*max[i-1],nums[i]*min[i-1]));
min[i]=Math.min(nums[i],Math.min(nums[i]*max[i-1],nums[i]*min[i-1]));
}
int res=Integer.MIN_VALUE;
for(int i=0;i<nums.length;i++){
res=Math.max(res,max[i]);
}
return res;
}
8.10 分隔字符串使同种字符出现在一起
力扣地址:https://leetcode-cn.com/problems/partition-labels/description/
解答:https://leetcode-cn.com/problems/partition-labels/solution/shou-hua-tu-jie-hua-fen-zi-mu-qu-jian-ji-lu-zui-yu/
public static List<Integer> partitionLabels(String s) {
Map<Character,Integer> map=new HashMap<>();
for(char c:s.toCharArray()){
map.put(c,s.lastIndexOf(c));
}
List<Integer> list=new ArrayList<>();
int cutStart=0;
int maxIndex=0;
for(int i=0;i<s.length();i++){
maxIndex=Math.max(maxIndex,map.get(s.charAt(i)));
if(i==maxIndex){
list.add(i-cutStart+1);
cutStart=i+1;
}
}
return list;
}
8.11 最长连续递增序列
力扣地址:https://leetcode-cn.com/problems/longest-continuous-increasing-subsequence/
public int findLengthOfLCIS(int[] nums) {
if(nums==null || nums.length==0){
return 0;
}
int begin=0;
int res=0;
for(int i=0;i<nums.length;i++){
if(i>0 && nums[i]<=nums[i-1]){
begin=i;
}
res=Math.max(res,i-begin+1);
}
return res;
}
九 二分法
9.1 求开方
力扣地址:https://leetcode-cn.com/problems/sqrtx/description/
解答:返回的是整数,而不是小数。
public int mySqrt(int x) {
if(x==0){
return 0;
}
int l=0;
int h=x;
int mid;
int ans=-1;
while(l<=h){
mid=l+(h-l)/2;
if((long)mid*mid<=x){
ans=mid;
l=mid+1;
}else{
h=mid-1;
}
}
return ans;
}
拓展题,如何指定精度求根号,返回的是小数
public static double mySqrt(double x,double pricsie) {
double l,h,mid;
l=0;
h=x;
mid=l+(h-l)/2;
while(mid*mid<x-pricsie || mid*mid>x+pricsie){
if(mid*mid<x-pricsie){
l=mid;
}else{
h=mid;
}
mid=l+(h-l)/2;
}
return mid;
}
9.2 大于给定元素的最小元素
力扣地址:https://leetcode-cn.com/problems/find-smallest-letter-greater-than-target/description/
解答:我们想要在有序数组中查找比目标字母大的最小字母,可以使用二分查找:让我们找到最右边的位置将 target
插入 letters
中,以便它保持排序。这其实是对原有问题的一个转化,使得问题的求解更加简单
**这个题为什么强调重复,就是因为就是给定的元素的可能会重复,所以不能直接找到Mid后,就返回mid+1。
public char nextGreatestLetter(char[] letters, char target) {
int l=0;
int h=letters.length-1;
while(l<=h){
int mid=l+(h-l)/2;
if(letters[mid]>target){
h=mid-1;
}else if(letters[mid]<target){
l=mid+1;
}else{
//要考虑重复
while(letters[(mid+1)%letters.length]==letters[(mid)%letters.length]){
mid++;
}
return letters[(mid+1)%letters.length];
}
}
return letters[(h+1)%letters.length];
}
9.3 有序数组的single element
力扣地址:https://leetcode-cn.com/problems/single-element-in-a-sorted-array/description/
解答:
解法一:采用位运算(异或)
public int singleNonDuplicate(int[] nums) {
int res=0;
for(int data:nums){
res^=data;
}
return res;
}
解法二:题目要求时间复杂度为log(N),因此异或不满足要求,使用二分法如下
public int singleNonDuplicate(int[] nums) {
int lo = 0;
int hi = nums.length - 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
boolean halvesAreEven = (hi - mid) % 2 == 0;
if (nums[mid + 1] == nums[mid]) {
if (halvesAreEven) {
lo = mid + 2;
} else {
hi = mid - 1;
}
} else if (nums[mid - 1] == nums[mid]) {
if (halvesAreEven) {
hi = mid - 2;
} else {
lo = mid + 1;
}
} else {
return nums[mid];
}
}
return nums[lo];
}
9.4 第一个错误的版本
力扣地址:https://leetcode-cn.com/problems/first-bad-version/description/
解答:
public int firstBadVersion(int n) {
int l=1;
int h=n;
int mid;
while(l<=h){
mid=l+(h-l)/2;
if(isBadVersion(mid)){
h=mid-1;
}else{
l=mid+1;
}
}
return l;
}
9.5 旋转数组的最小数字
力扣地址:https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/description/
解答:
public int findMin(int[] nums) {
int low = 0;
int high = nums.length - 1;
while (low < high) {
int pivot = low + (high - low) / 2;
if (nums[pivot] < nums[high]) {
high = pivot;
} else {
low = pivot + 1;
}
}
return nums[low];
}
9.6 查找区间
力扣地址: https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/
解答:
public int[] searchRange(int[] nums, int target) {
int b1=binarySearch1(nums,target);
int b2=binarySearch2(nums,target)-1;
if(b1<=b2 && nums[b1]==target&&nums[b2]==target){
return new int[]{b1,b2};
}else{
return new int[]{-1,-1};
}
}
public int binarySearch1(int[] nums, int target){
int n=nums.length;
int l=0;
int h=n-1;
int mid=0;
int ans=n;
while(l<=h){
mid=l+(h-l)/2;
if(nums[mid]>=target){
ans=mid;
h=mid-1;
}else{
l=mid+1;
}
}
return ans;
}
public int binarySearch2(int[] nums, int target){
int n=nums.length;
int l=0;
int h=n-1;
int mid=0;
int ans=n;
while(l<=h){
mid=l+(h-l)/2;
if(nums[mid]>target){
ans=mid;
h=mid-1;
}else{
l=mid+1;
}
}
return ans;
}
9.7 查找重复有序数组中最左边的目标数
解答:
public int search (int[] nums, int target) {
if(nums.length == 0) {
return -1;
}
int begin = 0;
int end = nums.length - 1;
while(begin < end) {
int mid = (begin + end) / 2;
if(nums[mid] < target) {
begin = mid + 1;
} else {
end = mid;
}
}
return nums[begin] == target ? begin : -1;
}
十 数学
10.1
10.1.1 生成素数序列
力扣地址:https://leetcode-cn.com/problems/count-primes/description/
解答:如果 x 是质数,那么大于 x 的 x的倍数 2x,3x,… 一定不是质数
public int countPrimes(int n) {
int[] isPrime=new int[n];
Arrays.fill(isPrime,1);
int res=0;
for(int i=2;i<n;i++){
if(isPrime[i]==1){
res++;
for(int step=2;step*i<n;step++){
isPrime[step*i]=0;
}
}
}
return res;
}
10.1.2 求最大公约数
//递归解法
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
对于 a 和 b 的最大公约数 f(a, b),有:
- 如果 a 和 b 均为偶数,f(a, b) = 2*f(a/2, b/2);
- 如果 a 是偶数 b 是奇数,f(a, b) = f(a/2, b);
- 如果 b 是偶数 a 是奇数,f(a, b) = f(a, b/2);
- 如果 a 和 b 均为奇数,f(a, b) = f(b, a-b);
public int gcd(int a, int b) {
if (a < b) {
return gcd(b, a);
}
if (b == 0) {
return a;
}
boolean isAEven = isEven(a), isBEven = isEven(b);
if (isAEven && isBEven) {
return 2 * gcd(a >> 1, b >> 1);
} else if (isAEven && !isBEven) {
return gcd(a >> 1, b);
} else if (!isAEven && isBEven) {
return gcd(a, b >> 1);
} else {
return gcd(b, a - b);
}
}
10.1.3 最小公倍数
int lcm(int a, int b) {
return a * b / gcd(a, b);
}
10.1.3 丑数
力扣地址:https://leetcode-cn.com/problems/ugly-number/submissions/
解答:
public boolean isUgly(int n) {
if(n<=0) return false;
int[] num=new int[]{2,3,5};
for(int factor:num){
while( n % factor ==0){
n/=factor;
}
}
return n==1;
}
10.2 进制转换
10.2.1 7进制
力扣地址:https://leetcode-cn.com/problems/base-7/description/
//短除法
public String convertToBase7(int num) {
if(num==0){
return "0";
}
int absnum=Math.abs(num);
StringBuilder sb=new StringBuilder();
while(absnum>0){
sb.append(absnum%7);
absnum/=7;
}
return num>0?sb.reverse().toString():"-"+sb.reverse().toString();
}
10.2.2 16机制
力扣地址:https://leetcode-cn.com/problems/convert-a-number-to-hexadecimal/description/
解答:
//这道题不应采用短除法,而是采用位运算
public String toHex(int num) {
if(num==0) return "0";
char[] mapping=new char[]{'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
StringBuilder sb=new StringBuilder();
while(num!=0){
sb.append(mapping[num & 0b1111]);//取出二进制的低四位
num>>>=4;//无符号右移
}
return sb.reverse().toString();
}
10.2.3 十进制转换为任意进制
思路就是短除法
public String convertToBase7(int num,int k) {
if(num==0){
return "0";
}
char[] mapping=new char[]{'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
int absnum=Math.abs(num);
StringBuilder sb=new StringBuilder();
while(absnum>0){
sb.append(mapping[absnum%k]);
absnum/=k;
}
return num>0?sb.reverse().toString():"-"+sb.reverse().toString();
}
10.3 阶乘
10.3.1 统计阶乘尾部有多少个 0
力扣地址:https://leetcode-cn.com/problems/factorial-trailing-zeroes/description/
解答:0的产生是因为5的出现,但是如果只统计N/5是不对的,因为5^2中有两个5,以此类推,
正确的是统计:N/5 + N/52 + N/53 + …,
public int trailingZeroes(int n) {
return n==0?0:n/5+trailingZeroes(n/5);
}
10.4 字符串加减法
10.4.1 二进制加法
力扣地址:https://leetcode-cn.com/problems/add-binary/description/
解答:
public static String addBinary(String a, String b) {
int carry=0;
int aindex=a.length()-1;
int bindex=b.length()-1;
StringBuilder sb=new StringBuilder();
while(aindex>=0 || bindex>=0 || carry==1){
if(aindex>=0 && a.charAt(aindex--)=='1'){
carry++;
}
if(bindex>=0 && b.charAt(bindex--)=='1'){
carry++;
}
sb.append(carry%2);
carry/=2;
}
return sb.reverse().toString();
}
10.4.2 字符串加法
力扣地址:https://leetcode-cn.com/problems/add-strings/description/
解答:
类似于上一题
public static String addStrings(String num1, String num2) {
int carry=0;
int n1=num1.length()-1;
int n2=num2.length()-1;
StringBuilder sb=new StringBuilder();
while(carry>0 || n1>=0 || n2>=0){
if(n1>=0){
carry+=num1.charAt(n1--)-'0';
}
if(n2>=0){
carry+=num2.charAt(n2--)-'0';
}
sb.append(carry%10);
carry /=10;
}
return sb.reverse().toString();
}
10.4.3 三数之和
力扣地址:https://leetcode-cn.com/problems/3sum/
int n=nums.length;
Arrays.sort(nums);
List<List<Integer>> list=new ArrayList<>();
for(int first=0;first<n;first++){
if(first>0 && nums[first]==nums[first-1]){
continue;
}
int third=n-1;
int target=-nums[first];
for(int second=first+1;second<n;second++){
if(second>first+1 && nums[second]==nums[second-1]){
continue;
}
while(third>second && nums[second]+nums[third]>target){
third--;
}
if(second==third) break;
if(nums[second]+nums[third]==target){
List<Integer> temp=new ArrayList<>();
temp.add(nums[first]);
temp.add(nums[second]);
temp.add(nums[third]);
list.add(temp);
}
}
}
return list;
}
10.5 相遇问题
10.5.1 改变数组元素使所有的数组元素都相等
力扣地址:https://leetcode-cn.com/problems/minimum-moves-to-equal-array-elements-ii/description/
解答:
寻找中位数,然后计算每个数与中位数的差值,再迭代将差值相加。
自己起初先对数据去重再求中位数,然而那种做法是错的,需要直接在原数组上寻找中位数。
public int minMoves2(int[] nums) {
Arrays.sort(nums);
int sum = 0;
for (int num : nums) {
sum += Math.abs(nums[nums.length / 2] - num);
}
return sum;
}
10.6 多数投票问题
10.6.1 数组中出现次数多于 n / 2 的元素
力扣地址:https://leetcode-cn.com/problems/majority-element/description/
解答:
方法1:用哈希表(自己采用的方法,比较麻烦)
方法2:直接排序,返回[n/2]出的值
10.7 其他题目
10.7.1 平方数
力扣地址:https://leetcode-cn.com/problems/valid-perfect-square/description/
解答:
public boolean isPerfectSquare(int num) {
if(num<2){
return true;
}
int l=2;
int r=num/2;
int mid;
while(l<=r){
mid=l+(r-l)/2;
if((long)mid*mid<num){//特别注意这个long类型的转换
l=mid+1;
}else if((long)mid*mid>num){
r=mid-1;
}else{
return true;
}
}
return false;
}
10.7.2 3的n次方
力扣地址:https://leetcode-cn.com/problems/power-of-three/description/
解答:
利用数学公式转换
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a1VmntzH-1652019362994)(C:\Users\pan\Desktop\笔记\notes\刷题笔记.assets\image-20210722142503682.png)]
public boolean isPowerOfThree(int n) {
System.out.println(Math.log(n) / Math.log(3));
return (Math.log(n) / Math.log(3)) % 1 == 0;
}
10.7.3 乘积数组
力扣地址:https://leetcode-cn.com/problems/product-of-array-except-self/description
解答:
public int[] productExceptSelf(int[] nums) {
int length = nums.length;
// L 和 R 分别表示左右两侧的乘积列表
int[] L = new int[length];
int[] R = new int[length];
int[] answer = new int[length];
// L[i] 为索引 i 左侧所有元素的乘积
// 对于索引为 '0' 的元素,因为左侧没有元素,所以 L[0] = 1
L[0] = 1;
for (int i = 1; i < length; i++) {
L[i] = nums[i - 1] * L[i - 1];
}
// R[i] 为索引 i 右侧所有元素的乘积
// 对于索引为 'length-1' 的元素,因为右侧没有元素,所以 R[length-1] = 1
R[length - 1] = 1;
for (int i = length - 2; i >= 0; i--) {
R[i] = nums[i + 1] * R[i + 1];
}
// 对于索引 i,除 nums[i] 之外其余各元素的乘积就是左侧所有元素的乘积乘以右侧所有元素的乘积
for (int i = 0; i < length; i++) {
answer[i] = L[i] * R[i];
}
return answer;
}
10.7.4 找出数组中的乘积最大的三个数
力扣地址:https://leetcode-cn.com/problems/maximum-product-of-three-numbers/description/
解答:
先排序,然后考虑数组中各种正负情况。
public int maximumProduct(int[] nums) {
Arrays.sort(nums);
int n = nums.length;
return Math.max(nums[0] * nums[1] * nums[n - 1], nums[n - 3] * nums[n - 2] * nums[n - 1]);
}
10.8 不用第三个元素,交换两个元素的值
public int maximumProduct(int a,int b) {
a=a ^ b;
b=a ^ b;
a=a ^ b;
}
十一 搜索
11.1 BFS(最短路劲等最优解问题)
在程序实现 BFS 时需要考虑以下问题:
- 队列:用来存储每一轮遍历得到的节点;
- 标记:对于遍历过的节点,应该将它标记,防止重复遍历。
11.1.1 计算在网格中从原点到特定点的最短路径长度
力扣地址: https://leetcode-cn.com/problems/shortest-path-in-binary-matrix/
解答:
public int shortestPathBinaryMatrix(int[][] grids) {
if(grids==null || grids.length==0 || grids[0].length==0){
return -1;
}
int m=grids.length;
int n=grids[0].length;
Queue<Pair<Integer,Integer>> queue=new LinkedList<>();
queue.add(new Pair<>(0,0));
int[][] directions={{1, -1}, {1, 0}, {1, 1}, {0, -1}, {0, 1}, {-1, -1}, {-1, 0}, {-1, 1}};
int path=0;
while(!queue.isEmpty()){
int size=queue.size();
path++;
while(size-->0){
Pair<Integer,Integer> pair=queue.poll();
int leftIndex=pair.getKey();
int rigthIndex=pair.getValue();
if(grids[leftIndex][rigthIndex]==1) continue;
if(leftIndex==m-1 && rigthIndex== n-1){
return path;
}
grids[leftIndex][rigthIndex]=1;//标志此节点已经比访问过
for(int[] d:directions){
int nl=leftIndex+d[0];
int nr=rigthIndex+d[1];
if(nl>=0 && nl<m && nr>=0 && nr<n){
queue.add(new Pair<>(nl,nr));
}
}
}
}
return -1;
}
11.1.2 组成整数的最小平方数数量
力扣地址:https://leetcode-cn.com/problems/perfect-squares/description/
解答:此题采用动态规划来做。
11.2 DFS(求解可达性问题)
在程序实现 DFS 时需要考虑以下问题:
- 栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。
- 标记:和 BFS 一样同样需要对已经遍历过的节点进行标记。
11.2.1 查找最大连通面积
力扣地址:https://leetcode-cn.com/problems/max-area-of-island/description/
解答:
class Solution {
int[][] directions={{-1,0},{0,1},{1,0},{0,-1}};
int m;
int n;
public int maxAreaOfIsland(int[][] grid) {
if(grid==null ||grid.length==0 || grid[0].length==0 ) return 0;
int MaxLand=0;
m=grid.length;
n=grid[0].length;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
MaxLand=Math.max(MaxLand,dfs(grid,i,j));
}
}
return MaxLand;
}
private int dfs(int[][] num,int i,int j){
if(i<0 || j<0 || i>=m || j>=n || num[i][j]==0){
return 0;
}
int land=1;
num[i][j]=0;
for(int[] d:directions){
land+=dfs(num,i+d[0],j+d[1]);
}
return land;
}
}
11.2.2 矩阵中的连通分量数目
力扣地址:https://leetcode-cn.com/problems/number-of-islands/description/
解答:
int[][] directions={{-1,0},{0,1},{1,0},{0,-1}};
public int numIslands(char[][] grid) {
if(grid ==null || grid.length==0 || grid[0].length==0){
return 0;
}
int land=0;
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[0].length;j++){
if(dfs(grid,i,j)){
land++;
}
}
}
return land;
}
public boolean dfs(char[][] grid,int i,int j){
if(i<0 || j<0 || i>=grid.length || j>=grid[0].length || grid[i][j]=='0'){
return false;
}
grid[i][j]='0';
for(int[] d:directions){
dfs(grid,i+d[0],j+d[1]);
}
return true;
}
11.2.3 好友关系的连通分量数目
力扣地址:https://leetcode-cn.com/problems/friend-circles/description/
解答:
int n;
public int findCircleNum(int[][] M) {
if(M==null || M.length==0) return 0;
n=M.length;
boolean[] visited=new boolean[n];
int circle=0;
for(int i=0;i<n;i++){
if(!visited[i]){
circle++;
dfs(M,i,visited);
}
}
return visited;
}
public void dfs(int[][] M,int i,boolean[] visited){
visited[i]=true;
for(int j=0;j<n;j++){
if(M[i][j]==0 || visited[j]){
continue;
}
dfs(M,j,visited);
}
}
11.3 回溯
回溯属于深度优先搜索
- 普通DFS主要解决可达性问题,只要执行到特定的位置然后返回即可
- 然而回溯主要用于求解排列组合问题,例如有 { ‘a’,‘b’,‘c’ } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程
因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
- 在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
- 但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。
11.3.1 数字键盘组合
力扣地址:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/descriptio
解答:
public List<String> letterCombinations(String digits) {
String[] key={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
List<String> combinations=new ArrayList<>();
if(digits==null || digits.length()==0) return combinations;
StringBuilder sb=new StringBuilder();
backTracing(combinations,digits,0,sb,key);
return combinations;
}
private void backTracing(List<String> combinations,String digits,int index,StringBuilder combination,String[] key){
if(index==digits.length()){
combinations.add(combination.toString());
return;
}
char[] letters=key[digits.charAt(index)-'0'].toCharArray();
for(char c:letters){
combination.append(c);
backTracing(combinations,digits,index+1,combination,key);
combination.deleteCharAt(index);
}
}
11.3.2 IP地址划分
力扣地址:https://leetcode-cn.com/problems/restore-ip-addresses/description/
解答:
List<String> ipAddress=new ArrayList<>();
final int SEG_COUNT=4;
int[] ip=new int[SEG_COUNT];
public List<String> restoreIpAddresses(String s) {
if(s==null || s.length()==0){
return ipAddress;
}
backTracing(s,0,0);
return ipAddress;
}
public void backTracing(String s,int segId,int start){
if(segId==SEG_COUNT){
if(start==s.length()){
StringBuilder sb=new StringBuilder();
for(int i=0;i<SEG_COUNT;i++){
sb.append(ip[i]);
if(i!=SEG_COUNT-1){
sb.append(".");
}
}
ipAddress.add(sb.toString());
}
return;
}
if(start==s.length()){
return;
}
if(s.charAt(start)=='0'){
ip[segId]=0;
backTracing(s,segId+1,start+1);
}
int addr=0;
for(int begin=start;begin<s.length();begin++){
addr=addr*10+(s.charAt(begin)-'0');
if(addr>0 && addr<=255){
ip[segId]=addr;
backTracing(s,segId+1,begin+1);
}else{
break;
}
}
}
11.3.3 组合
力扣地址:https://leetcode-cn.com/problems/combinations/description/
解答:
List<List<Integer>> combinations=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backTracing(combinations,temp,n,1,0,k);
return combinations;
}
public void backTracing(List<List<Integer>> combinations,List<Integer> temp,int n,int start,int seg,int k){
if(seg==k){
combinations.add(new ArrayList<>(temp));
return;
}
if(start>n){
return;
}
for(int begin=start;begin<=n;begin++){
temp.add(begin);
backTracing(combinations,temp,n,begin+1,seg+1,k);
temp.remove(temp.size()-1);
}
}
11.3.4 组合求和
力扣地址:https://leetcode-cn.com/problems/combination-sum/description/
解答:
List<List<Integer>> combinations=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if(candidates==null || candidates.length==0) return combinations;
backTracing(candidates,0,target);
return combinations;
}
public void backTracing(int[] candidates,int start,int target){
if(target==0){
combinations.add(new ArrayList(temp));
}
if(target<0){//特别注意的情况就是target<0,不然会导致代码死循环。
return;
}
if(start==candidates.length){
return;
}
for(int begin=start;begin<candidates.length;begin++){
temp.add(candidates[begin]);
backTracing(candidates,begin,target-candidates[begin]);
temp.remove(temp.size()-1);
}
}
11.3.5 含有相同元素求子集
力扣地址:https://leetcode-cn.com/problems/subsets-ii/
解答:考虑数组 [1,2,2],选择前两个数,或者第一、三个数,都会得到相同的子集。也就是说,对于当前选择的数 x,若前面有与其相同的数 y,且没有选择 y,此时包含 x 的子集,必然会出现在包含 y 的所有子集中。我们可以通过判断这种情况,来避免生成重复的子集。代码实现时,可以先将数组排序;迭代时,若发现没有选择上一个数,且当前数字与上一个数相同,则可以跳过当前生成的子集。
List<List<Integer>> result=new ArrayList<>();
List<Integer> path=new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
result.add(new ArrayList<>());
boolean[] hasVisited=new boolean[nums.length];
backTracing(nums,0,hasVisited);
return result;
}
public void backTracing(int[] nums,int start,boolean[] hasVisited){
if(start>=nums.length){
return;
}
for(int i=start;i<nums.length;i++){
if(i!=0 && nums[i]==nums[i-1] && !hasVisited[i-1]){
continue;//去重操作
}
path.add(nums[i]);
hasVisited[i]=true;
result.add(new ArrayList(path));
backTracing(nums,i+1,hasVisited);
hasVisited[i]=false;
path.remove(path.size()-1);
}
}
类似题目:https://leetcode-cn.com/problems/subsets/
解答:
List<List<Integer>> res=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
res.add(new ArrayList<>());
if(nums==null || nums.length==0){
return res;
}
backTracing(nums,0);
return res;
}
private void backTracing(int[] nums,int start){
if(start>=nums.length){
return;
}
for(int i=start;i<nums.length;i++){
temp.add(nums[i]);
res.add(new ArrayList(temp));
backTracing(nums,i+1);
temp.remove(temp.size()-1);
}
}
十二 分治
12.1 给表达式加括号
力扣地址:https://leetcode-cn.com/problems/different-ways-to-add-parentheses/description/
解答:本题归于分治算法,形如x op y的表达式而言,他的结果能有多少组合取决于x,y的结果的个数,就x单独而言,他又可以看做是x1 op y1,这样就体验的分而治之的策略
public List<Integer> diffWaysToCompute(String expression) {
List<Integer> ways=new ArrayList<>();
for(int i=0;i<expression.length();i++){
char c=expression.charAt(i);
if(c=='+' || c=='-' || c=='*'){
List<Integer> left=diffWaysToCompute(expression.substring(0,i));
List<Integer> right=diffWaysToCompute(expression.substring(i+1));
for(int l:left){
for(int r:right){
switch(c) {
case '+':
ways.add(l+r);
break;
case '-':
ways.add(l-r);
break;
case '*':
ways.add(l*r);
break;
}
}
}
}
}
if(ways.size()==0){
ways.add(Integer.parseInt(expression));
}
return ways;
}
12.2 不同的二叉搜索树
力扣地址:https://leetcode-cn.com/problems/unique-binary-search-trees-ii/description/
解答:
public List<TreeNode> generateTrees(int n) {
if(n<1){
return new ArrayList<>();
}
return bulidTree(1,n);
}
public List<TreeNode> bulidTree(int l,int r){
List<TreeNode> list=new ArrayList<>();
if(l>r){
list.add(null);
return list;
}
for(int i=l;i<=r;i++){
List<TreeNode> left=bulidTree(l,i-1);
List<TreeNode> right=bulidTree(i+1,r);
for(TreeNode ln:left){
for(TreeNode rn:right){
TreeNode root=new TreeNode(i);//每次都要创建新的对象放入
root.left=ln;
root.right=rn;
list.add(root);
}
}
}
return list;
}
十三 数组与矩阵
13.1 把数组中的 0 移到末尾
力扣地址:https://leetcode-cn.com/problems/move-zeroes/description/
解答:设置两个指针left,right,left指向已经处理好的数组的末尾,right指向下一个非0的元素,left,right之间元素全为0,最后将left right对应的元素交换
public void moveZeroes(int[] nums) {
int n=nums.length;
int left;
int right;
for(left=0,right=0;left<n&&right<n;){
if(nums[right]==0){//直到遇到0元素,两个指针之间会岔开
right++;
}else{//如果两个指针所指元素都非0,则一起移动
swap(nums,left,right);
left++;
right++;
}
}
}
public void swap(int[] nums,int i,int j){
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
13.2 改变矩阵维度
力扣地址:https://leetcode-cn.com/problems/reshape-the-matrix/description/
解答:
思路是比较简单的,不过如何用一个index就来定位二维数组中的元素是个特点。
index/nums.length就是定位二维数组的第一个维度,index%nums.length就是定位数组的第二个维度
public int[][] matrixReshape(int[][] nums, int r, int c) {
int n=nums.length;
int m=nums[0].length;
if(m*n !=r*c){
return nums;
}
int[][] newArr=new int[r][c];
int index=0;
for(int i=0;i<r;i++){
for(int j=0;j<c;j++){
newArr[i][j]=nums[index/m][index%m];
index++;
}
}
return newArr;
}
13.3 找出数组中最长的连续 1
地址:https://leetcode-cn.com/problems/max-consecutive-ones/description/
一次遍历,中途缓存最大值就行
public int findMaxConsecutiveOnes(int[] nums) {
int res=-1;
int temp=0;
for(int i=0;i<nums.length;i++){
if(nums[i]==1){
temp++;
}else{
res=Math.max(res,temp);
temp=0;
}
}
res=Math.max(res,temp);
return res;
}
13.4 有序矩阵查找
力扣地址:https://leetcode-cn.com/problems/search-a-2d-matrix-ii/description/
自己思路也很简单,不过官方答案更优,从左下角的元素出发
首先,我们初始化一个指向矩阵左下角的 (row,col)(row,col) 指针。然后,直到找到目标并返回 true(或者指针指向矩阵维度之外的 (row,col)(row,col) 为止,我们执行以下操作:如果当前指向的值大于目标值,则可以 “向上” 移动一行。 否则,如果当前指向的值小于目标值,则可以移动一列。不难理解为什么这样做永远不会删减正确的答案;因为行是从左到右排序的,所以我们知道当前值右侧的每个值都较大。 因此,如果当前值已经大于目标值,我们知道它右边的每个值会比较大。也可以对列进行非常类似的论证,因此这种搜索方式将始终在矩阵中找到目标(如果存在)
public boolean searchMatrix(int[][] matrix, int target) {
int row=matrix.length-1;
int col=0;
while(row>=0 &&col<=matrix[0].length){
if(matrix[row][col]<target){
col++;
}else if(matrix[row][col]>target){
row--;
}else{
return true;
}
}
return false;
}
13.5 有序矩阵的 Kth Element
力扣地址:https://leetcode-cn.com/problems/kth-smallest-element-in-a-sorted-matrix/description/
解答:
自己的思路很简单,从每一列是去筛选,最差的情况就是遍历矩阵中的每一个元素。
官方给出了一个二分法的解法
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) {
num += i + 1;
j++;
} else {
i--;
}
}
return num >= k;
}
13.6 一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出重复的数和丢失的数
力扣地址:https://leetcode-cn.com/problems/set-mismatch/description/
首先的思路是排序,太过于简单先不写。
其次的思想就是交换,把正确的元素放在自己该放的位置上,不过这个过程是层层深入的
public int[] findErrorNums(int[] nums) {
for(int i=0;i<nums.length;i++){
while(nums[i]!=i+1 && nums[nums[i]-1]!=nums[i]){
sawp(nums,i,nums[i]-1);
}
for(int i=0;i<nums.length;i++){
if(nums[i]!=i+1){
return new int[]{nums[i],i+1};
}
}
return null;
}
}
private void swap(int[] nums, int i, int j) {
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
13.7 找出数组中重复的数,数组值在 [1, n] 之间
力扣地址:https://leetcode-cn.com/problems/find-the-duplicate-number/description/
自己用位运算的思路有问题,因为只虽然只有一个数重复,但是这个数重复多少次并没有规定。所以改为用二分法
,设置一个count[i],代表小于等于i的数的个数,如果小于等于i的数的个数大于i,说明i或者i之前的数一定包含重复了。
public int findDuplicate(int[] nums) {
int n=nums.length;
int l=1;
int r=n-1;
int ans=0;
while(l<=r){
int mid=l+(r-l)/2;
int count=0;
for(int i=0;i<n;i++){
if(nums[i]<=mid){
count++;
}
}
if(count<=mid){
l=mid+1;
}else{
r=mid-1;
ans=mid;//mid才是我每次的答案,不是r
}
}
return ans;
}
13.8 数组相邻差值的个数
力扣地址:https://leetcode-cn.com/problems/beautiful-arrangement-ii/description/
解答:
public int[] constructArray(int n, int k) {
int[] res=new int[n];
for(int i=0;i<n;i++){
res[i]=i+1;
}
for(int j=1;j<k;j++){
reverse(res,j,n-1);
}
return res;
}
//翻转数组[i,j]之间的数
void reverse(int[] res, int i, int j){
while(i < j){
int t = res[i];
res[i] = res[j];
res[j] = t;
i++;
j--;
}
}
13.9 数组的度
力扣地址:https://leetcode-cn.com/problems/degree-of-an-array/description/
解答:
public int findShortestSubArray(int[] nums) {
Map<Integer,int[]> map=new HashMap<>();
for(int i=0;i<nums.length;i++){
if(!map.containsKey(nums[i])){
map.put(nums[i],new int[]{1,i,i});
}else{
int[] indexArr=map.get(nums[i]);
indexArr[0]++;
indexArr[2]=i;
}
}
int maxCount=0;
int minLen=Integer.MAX_VALUE;
for(Map.Entry<Integer,int[]> entry:map.entrySet()){
int[] value=entry.getValue();
if(value[0]>maxCount){
minLen=value[2]-value[1]+1;
maxCount=value[0];
}else if(value[0]==maxCount){
minLen=Math.min(minLen,value[2]-value[1]+1);
}
}
return minLen;
}
13.10 对角元素相等的矩阵
力扣地址:https://leetcode-cn.com/problems/toeplitz-matrix/description/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z0UkjKHE-1652019362995)(C:\Users\pan\Desktop\笔记\notes\刷题笔记.assets\image-20210806213414446.png)]
解答:我一直在思考如何从(0,0)入手,但是此题的入口应该设在(1,1),去比较nums(i,j)与nums(i-1,j-1),如果不相等,直接返回false;
public boolean isToeplitzMatrix(int[][] matrix) {
for(int i=1;i<matrix.length;i++){
for(int j=1;j<matrix[0].length;j++){//从nums[1][1]开始遍历
if(matrix[i][j]!=matrix[i-1][j-1]){
return false;
}
}
}
return true;
}
13.11 嵌套数组
力扣地址:https://leetcode-cn.com/problems/array-nesting/description/
解答:
思想很简单,暴力会超出时间限制,所以要采用访问标记,避免重复计算。
public int arrayNesting(int[] nums) {
List<Integer> list=new ArrayList<>();
int n=nums.length;
boolean[] visited=new boolean[n];
int res=0;
for(int i=0;i<n;i++){
if(!visited[i]){
visited[i]=true;
int temp=nums[i];
list.add(temp);
while(!visited[temp]){
visited[temp]=true;
list.add(temp);
temp=nums[temp];
}
res=Math.max(res,list.size());
list.removeAll(list);
}
}
return res;
}
13.12 分隔数组(背)
力扣地址:https://leetcode-cn.com/problems/max-chunks-to-make-sorted/description/
解答:本题自己没有思路,借鉴网友和官方的思路。
假如0-pos可以分块,则0-pos之间必须包含了0-pos的所有数,我们每次记录目前遍历到的最大值,当这个最大值等于pos的,此时就可以成块了(块数++)
首先需要明确下标和元素的值一致,块数就要加1。
public int maxChunksToSorted(int[] arr) {
int ans = 0, max = 0;
for (int i = 0; i < arr.length; ++i) {
max = Math.max(max, arr[i]);
if (max == i) ans++;
}
return ans;
}
13.13 螺旋矩阵
自己没有思路,官方思路是按层模拟。可以将矩阵看成若干层,首先输出最外层的元素,其次输出次外层的元素,直到输出最内层的元素。
从左到右遍历上侧元素,依次为 (top,left) 到(top,right)
从上到下遍历右侧元素,依次为 (top+1,right)到(bottom,right)
如果 left<right && bottom<top,则从右到左,从(bottom,right-1)到(bottom,left+1),然后从下到上,(bottom,left)到(top+1,left)
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> list=new ArrayList<>();
if(matrix==null || matrix.length==0 || matrix[0].length==0){
return list;
}
int left=0;
int right=matrix[0].length-1;
int top=0;
int bottom=matrix.length-1;
while(left<=right && top<=bottom){
for(int i=left;i<=right;i++){
list.add(matrix[top][i]);
}
for(int i=top+1;i<=bottom;i++){
list.add(matrix[i][right]);
}
if(left<right && top<bottom){
for(int i=right-1;i>=left;i--){
list.add(matrix[bottom][i]);
}
for(int i=bottom-1;i>top;i--){
list.add(matrix[i][left]);
}
}
left++;
right--;
top++;
bottom--;
}
return list;
}
13.14 合并区间
力扣地址:https://leetcode-cn.com/problems/merge-intervals/
解答:
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals,Comparator.comparingInt(o->o[0]));
int n=intervals.length;
List<int[]> list=new ArrayList<>();
list.add(intervals[0]);
for(int i=1;i<n;i++){
int size=list.size();
if(intervals[i][0]<=list.get(size-1)[1]){
list.get(size-1)[1]=Math.max(intervals[i][1],list.get(size-1)[1]);
}else{
list.add(intervals[i]);
}
}
int index=0;
int[][] res=new int[list.size()][];
for(int[] temp:list){
res[index++]=temp;
}
return res;
}
13.15 用数组中数组成一个最大数
力扣地址:https://leetcode-cn.com/problems/largest-number/
public String largestNumber(int[] nums) {
int n=nums.length;
String[] str=new String[n];
for(int i=0;i<n;i++){
str[i]=nums[i]+"";
}
Arrays.sort(str,(x,y)->(y+x).compareTo(x+y));
StringBuilder sb=new StringBuilder();
if(str[0].equals("0")){
return "0";
}
for(String s:str){
sb.append(s);
}
return sb.toString();
}
13.16 缺失的第一个正数
力扣地址:https://leetcode-cn.com/problems/first-missing-positive/
public int firstMissingPositive(int[] nums) {
int n=nums.length;
for(int i=0;i<n;i++){
while(nums[i]>0 && nums[i]<=n && nums[i]!=nums[nums[i]-1]){
int temp=nums[nums[i]-1];
nums[nums[i]-1]=nums[i];
nums[i]=temp;
}
}
for(int i=0;i<n;i++){
if(nums[i]!=i+1){
return i+1;
}
}
return n+1;
}
十四 双指针
14.1 删除有序数组中的重复项
力扣地址:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/
解答:
定义两个指针 fast 和 slow 分别为快指针和慢指针,快指针表示遍历数组到达的下标位置,慢指针表示下一个不同元素要填入的下标位置,初始时两个指针都指向下标 1。假设数组 nums 的长度为 n。将快指针fast 依次遍历从 1 到 n-1的每个位置,对于每个位置,如果nums[fast] !=nums[fast-1],说明 nums[fast]和之前的元素都不同,因此将 nums[fast]的值复制到 nums[slow]然后将 slow 的值加 1,即指向下一个位置。
public int removeDuplicates(int[] nums) {
if(nums==null || nums.length==0) {
return 0;
}
int n=nums.length;
int slow=1;
int fast=1;
while(fast<n){
if(nums[fast]!=nums[fast-1]){
nums[slow]=nums[fast];
slow++;
}
fast++;
}
return slow;
}
14.2 合并两个有序数组
力扣地址:https://leetcode-cn.com/problems/merge-sorted-array/
解答:
public static void merge(int[] nums1, int m, int[] nums2, int n) {
int[] temp=new int[nums1.length];
int l1=0;
int l2=0;
int index=0;
while(l1<m && l2<n){
if(nums1[l1]<nums2[l2]){
temp[index++]=nums1[l1++];
}else{
temp[index++]=nums2[l2++];
}
}
while(l1<m){
temp[index++]=nums1[l1++];
}
while(l2<n){
temp[index++]=nums2[l2++];
}
for(int i=0;i<nums1.length;i++){
nums1[i]=temp[i];
}
}