写在前面
本人22届 末流985机械硕士转码
拿到了 快手、美团、度小满、新浪、东方财富、用友offer
这些是我当时的刷题笔记
链表太简单了 就没写思路
后面的大部分题 都先写思路再写解题代码
算法这里是大头,必须要重视
这101道都是牛客上企业常考的题,我反复刷了5遍
面试的时候也碰见不少原题
比如:重建二叉树、第k个最大数、LRU、删除链表重复节点(二)
基本上这101道能写的很熟练 那应届找工作算法这块就没问题了
加油 年轻人!
一 链表
BM1反转链表
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null||head.next==null) return head;
ListNode pre=null;
ListNode cur=head;
while(cur!=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
}
BM2链表内指定区间反转
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
public ListNode reverseBetween (ListNode head, int m, int n) {
ListNode begin=head;
ListNode yummy=new ListNode(0);
yummy.next=head;
ListNode yumtemp=yummy;
for(int i=0;i<m-1;i++){
begin=begin.next;
yumtemp=yumtemp.next;
}
for(int i=0;i<(n-m);i++){
ListNode temp=begin.next;
begin.next=temp.next;
temp.next=yumtemp.next;
yumtemp.next=temp;
}
return yummy.next;
}
}
BM3链表中的节点每k个一组翻转
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
public ListNode reverseKGroup (ListNode head, int k) {
if(head==null||k==0||head.next==null) return head;
int len=getListLength(head);
int reverseNum=len/k;
for(int i=1;i<=reverseNum;i++){
head=reverseRange(head, (i-1)*k+1 ,i*k);
}
return head;
}
public int getListLength(ListNode node){
int len=0;
if(node==null) return len;
ListNode cur=node;
while(cur!=null){
len++;
cur=cur.next;
}
return len;
}
public ListNode reverseRange(ListNode head, int start,int end){
ListNode yummy=new ListNode(0);
yummy.next=head;
ListNode pre=yummy;
ListNode cur=head;
for(int i=0;i<start-1;i++){
pre=pre.next;
cur=cur.next;
}
for(int i=0;i<(end-start);i++){
ListNode temp=cur.next;
cur.next=temp.next;
temp.next=pre.next;
pre.next=temp;
}
return yummy.next;
}
}
BM4 合并两个排序的链表
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode node1=list1;
ListNode node2=list2;
ListNode res=new ListNode(0);
ListNode restemp=res;
while(node1!=null&&node2!=null){
if(node1.val<node2.val){
restemp.next=node1;
node1=node1.next;
}else{
restemp.next=node2;
node2=node2.next;
}
restemp=restemp.next;
}
if(node1!=null){
restemp.next=node1;
}
if(node2!=null){
restemp.next=node2;
}
return res.next;
}
}
BM5 合并k个已排序的链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
import java.util.*;
public class Solution {
public ListNode mergeKLists(ArrayList<ListNode> lists) {
LinkedList<ListNode> queue=new LinkedList<ListNode>();
for(ListNode l:lists) queue.addLast(l);
while(queue.size()>1){
queue.addFirst(mergeDoubleList( queue.pop(),queue.pop()));
}
return queue.isEmpty()?null:queue.poll();
}
public ListNode mergeDoubleList(ListNode node1,ListNode node2){
if(node1==null||node2==null) return node1==null?node2:node1;
ListNode temp1=node1;
ListNode temp2=node2;
ListNode res=new ListNode(0);
ListNode cur=res;
while(temp1!=null&&temp2!=null){
if(temp1.val<temp2.val){
cur.next=temp1;
temp1=temp1.next;
cur=cur.next;
cur.next=null;
}else{
cur.next=temp2;
temp2=temp2.next;
cur=cur.next;
cur.next=null;
}
if(temp1!=null){
cur.next=temp1;
}
if(temp2!=null){
cur.next=temp2;
}
}
return res.next;
}
}
BM6 判断链表中是否有环
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow) break;
}
if(fast==null||fast.next==null) return false;
return true;
}
}
BM7 链表中环的入口结点
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead) {
ListNode slow=pHead;
ListNode fast=pHead;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow) break;
}
//判断是否有环
if(fast==null||fast.next==null) return null;
slow=pHead;
while(slow!=fast){
slow=slow.next;
fast=fast.next;
}
return fast;
}
}
BM8 链表中倒数最后k个结点
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
public ListNode FindKthToTail (ListNode pHead, int k) {
// write code here
ListNode post=pHead;
ListNode pre=pHead;
for(int i=0;i<k;i++){
if(pre==null) return null;
pre=pre.next;
}
while(pre!=null){
pre=pre.next;
post=post.next;
}
return post;
}
}
BM9 删除链表的倒数第n个节点
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
public ListNode removeNthFromEnd (ListNode head, int n) {
// write code here
ListNode dummy=new ListNode(0);
dummy.next=head;
ListNode post=head;
ListNode pre=head;
ListNode beforepost=dummy;
for(int i=1;i<=n;i++){
pre=pre.next;
}
while(pre!=null){
pre=pre.next;
post=post.next;
beforepost=beforepost.next;
}
beforepost.next=post.next;
return dummy.next;
}
}
BM10 两个链表的第一个公共结点
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode tempA=pHead1;
ListNode tempB=pHead2;
while(tempA!=tempB){
tempA=tempA==null?pHead2:tempA.next;
tempB=tempB==null?pHead1:tempB.next;
}
return tempA;
}
}
BM11 链表相加(二)
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
public ListNode addInList (ListNode head1, ListNode head2) {
head1=reverse(head1);
head2=reverse(head2);
ListNode res=new ListNode(0);
ListNode restemp=res;
ListNode temp1=head1;
ListNode temp2=head2;
int add=0;
int sum=0;
while(temp1!=null&&temp2!=null){
sum=temp1.val+temp2.val+add;
ListNode temp=new ListNode(sum%10);
add=sum/10;
restemp.next=temp;
restemp=restemp.next;
temp1=temp1.next;
temp2=temp2.next;
}
while(temp1!=null){
sum=temp1.val+add;
ListNode t1=new ListNode(sum%10);
add=sum/10;
restemp.next=t1;
restemp=restemp.next;
temp1=temp1.next;
}
while(temp2!=null){
sum=temp2.val+add;
ListNode t2=new ListNode(sum%10);
add=sum/10;
restemp.next=t2;
restemp=restemp.next;
temp2=temp2.next;
}
if(add!=0){
restemp.next=new ListNode(add);
}
return reverse(res.next);
}
public ListNode reverse(ListNode list){
if(list==null||list.next==null) return list;
ListNode pre=null;
ListNode cur=list;
while(cur!=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
}
BM12 单链表的排序
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
* @param head ListNode类 the head node
* @return ListNode类
*/
public ListNode sortInList (ListNode head) {
if(head==null||head.next==null) return head;
return parted(head);
}
public ListNode parted(ListNode head){
if(head==null||head.next==null) return head;
ListNode slow=head;
ListNode fast=head.next.next;
while(fast!=null && fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
ListNode second=slow.next;
ListNode first=head;
slow.next=null;
first=parted(first);
second=parted(second);
return merge(first,second);
}
public ListNode merge(ListNode head1,ListNode head2){
if(head1==null||head2==null) return head1==null?head2:head1;
ListNode temp1=head1;
ListNode temp2=head2;
ListNode res=new ListNode(0);
ListNode restemp=res;
while(temp1!=null&&temp2!=null){
if(temp1.val<temp2.val){
restemp.next=temp1;
temp1=temp1.next;
restemp=restemp.next;
restemp.next=null;
}else{
restemp.next=temp2;
temp2=temp2.next;
restemp=restemp.next;
restemp.next=null;
}
}
if(temp1!=null){
restemp.next=temp1;
}
if(temp2!=null){
restemp.next=temp2;
}
return res.next;
}
}
BM13 判断一个链表是否为回文结构
思路:找到链表中点,反转后半段,然后前后指针比较
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
public boolean isPail (ListNode head) {
if(head==null||head.next==null) return true;
ListNode slow=head;
ListNode fast=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
ListNode second=slow.next;
ListNode first=head;
slow.next=null;
second=resverse(second);
while(first!=null&&second!=null){
if(first.val!=second.val) return false;
first=first.next;
second=second.next;
}
return true;
}
public ListNode resverse(ListNode head){
if(head==null||head.next==null) return head;
ListNode pre=null;
ListNode cur=head;
while(cur!=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
}
BM14 链表的奇偶重排
奇数索引的结点在前,偶数索引的结点在后
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
public ListNode oddEvenList (ListNode head) {
if(head==null||head.next==null) return head;
ListNode even=new ListNode(0);
ListNode odd=new ListNode(1);
ListNode eventemp=even;
ListNode oddtemp=odd;
ListNode headtemp=head;
int i=1;
while(headtemp!=null){
if(i%2==1){
eventemp.next=headtemp;
headtemp=headtemp.next;
eventemp=eventemp.next;
eventemp.next=null;
}else{
oddtemp.next=headtemp;
headtemp=headtemp.next;
oddtemp=oddtemp.next;
oddtemp.next=null;
}
i++;
}
eventemp.next=odd.next;
return even.next;
}
}
BM15 删除有序链表中的重复元素-Ⅰ
描述
删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次
例如:
给出的链表为1→1→2,返回1→2.
给出的链表为1→1→2→3→3,返回1→2→3.
数据范围:链表长度满足1000≤n≤100,链表中任意节点的值满足∣val∣≤100
进阶:空间复杂度 O(1),时间复杂度 O(n)
示例1
输入:{1,1,2}
返回值:{1,2}
示例2
输入:{}
返回值:{}
思路:
判断当前结点和当前结点的下一个结点是否相等,相等则改变当前节点的下一个结点向后移,直到不相等。
import java.util.*;
public class Solution {
public ListNode deleteDuplicates (ListNode head) {
// write code here
ListNode cur=head;
while(cur!=null){
while(cur.next!=null&&cur.val==cur.next.val){
cur.next=cur.next.next;
}
cur=cur.next;
}
return head;
}
}
BM16 删除链表中的重复元素-Ⅱ
描述
给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。
例如:
给出的链表为1→2→3→3→4→4→5, 返回1→2→5.
给出的链表为1→1→1→2→3, 返回2→3.
数据范围:链表长度 100000≤n≤10000,链表中的值满足 val≤1000
要求:空间复杂度 O(n),时间复杂度 O(n)
进阶:空间复杂度 O(1),时间复杂度 O(n)
示例1
输入:{1,2,2}
返回值:{1}
示例2
输入:{}
返回值:{}
思路:
1 需要前后两个指针
2 如果前后两个指针重复 需要找到最后一个重复的结点
3 修改指针指向
思路:
1 判断是否重复 重复寻找下一个不同值 不重复直接添加到链表尾部
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
public ListNode deleteDuplicates (ListNode head) {
ListNode dummy=new ListNode(0);
dummy.next=head;
ListNode pre=dummy;
ListNode p=head;
while(p!=null&&p.next!=null){
if(p.val==p.next.val){
while(p.next!=null&&p.val==p.next.val){
p=p.next;
}
pre.next=p.next;
p=p.next;
}else{
pre=p;
p=p.next;
}
}
return dummy.next;
}
}
public class Solution {
public ListNode deleteDuplicates (ListNode head) {
ListNode dummy=new ListNode(0);
ListNode temp=dummy;
ListNode p=head;
while(p!=null){
if(judgeDuplicate(p)){
int v=p.val;
while(p!=null && p.val==v) p=p.next;
}else{
temp.next=p;
p=p.next;
temp=temp.next;
temp.next=null;
}
}
return dummy.next;
}
public boolean judgeDuplicate(ListNode node){
if(node==null||node.next==null) return false;
if(node.next.val==node.val) return true;
return false;
}
}
二 数组
BM17 二分查找-I
import java.util.*;
public class Solution {
public int search (int[] nums, int target) {
int i = Arrays.binarySearch(nums, target);
return i<0?-1:i;
}
}
BM20 数组中的逆序对
描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出 P mod 1000000007
数据范围: 对于50% 的数据, size≤10^4
对于100% 的数据,size≤10^5
数组中所有数字的值满足0≤val≤1000000
要求:空间复杂度O(n),时间复杂度O(nlogn)
例1
输入:[1,2,3,4,5,6,7,0]
返回值:7
归并排序
先拆分,再合并
public class Solution {
static int res=0;
public int InversePairs(int [] array) {
mergeSort(array,0,array.length-1);
return res;
}
public void mergeSort(int[] array,int begin,int last){
if(begin>=last) return;
int mid=begin+(last-begin)/2;
mergeSort(array,begin,mid);
mergeSort(array,mid+1,last);
merge(array,begin,mid,last);
}
public void merge(int[] array,int begin,int mid,int last){
int[] tempArray=new int[last-begin+1];
int i=begin,j=mid+1,k=0;
while(i<=mid&&j<=last){
if(array[i]<=array[j]){
tempArray[k++]=array[i++];
}else{
tempArray[k++]=array[j++];
res=(res+mid-i+1)%1000000007;
}
}
while(j<=last){
tempArray[k++]=array[j++];
}
while(i<=mid){
tempArray[k++]=array[i++];
}
for(int k1=0;k1<tempArray.length;k1++){
array[begin+k1]=tempArray[k1];
}
}
}
BM21 旋转数组的最小数字
描述
有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。数据范围:1≤n≤10000,数组中任意元素的值: 0≤val≤10000
要求:空间复杂度:O(1) ,时间复杂度:O(logn)
示例1
输入:[3,4,5,1,2]
返回值:1
示例2
输入:[3,100,200,3]
返回值:3
思路:
例子:[3,4,5,1,2]
二分法:取中间的数和右侧比较
大于:left=mid+1
小于:right=mid
等于:right--
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
int left=0,right=array.length-1;
while(left<right){
int mid=(left+right)/2;
if(array[mid]<array[right]){
right=mid;
}else if(array[mid]>array[right]){
left=mid+1;
}else{
right--;
}
}
return array[left];
}
}
BM22 比较版本号
描述
牛客项目发布项目版本时会有版本号,比如1.02.11,2.14.4等等
现在给你2个版本号version1和version2,请你比较他们的大小
版本号是由修订号组成,修订号与修订号之间由一个"."连接。1个修订号可能有多位数字组成,修订号可能包含前导0,且是合法的。例如,1.02.11,0.1,0.2都是合法的版本号
每个版本号至少包含1个修订号。
修订号从左到右编号,下标从0开始,最左边的修订号下标为0,下一个修订号下标为1,以此类推。
比较规则:
一. 比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较忽略任何前导零后的整数值。比如"0.1"和"0.01"的版本号是相等的
二. 如果版本号没有指定某个下标处的修订号,则该修订号视为0。例如,"1.1"的版本号小于"1.1.1"。因为"1.1"的版本号相当于"1.1.0",第3位修订号的下标为0,小于1
三. version1 > version2 返回1,如果 version1 < version2 返回-1,不然返回0.
数据范围:len(version1),len(version2)≤1000,版本号中每一节可能超过int的表达范围
进阶: 空间复杂度 O(1) , 时间复杂度 O(n)
思路1:
字符串拆分并比较 ,无法通过,大数溢出
时间复杂度O(n),空间复杂度O(n)
思路2:
双指针,逐个解析
时间复杂度O(n),空间复杂度O(1)
public int compare (String version1, String version2) {
String[] str1s=version1.split("\\.");
String[] str2s=version2.split("\\.");
int i=0,j=0;
while(i<str1s.length || j<str2s.length){
int cur1=0;
int cur2=0;
if(i<str1s.length) cur1=Integer.parseInt(str1s[i]);
if(j<str2s.length) cur2=Integer.parseInt(str2s[j]);
if(cur1<cur2){
return -1;
}else if(cur1>cur2){
return 1;
}
i++;
j++;
}
return 0;
}
import java.util.*;
public class Solution {
public int compare (String version1, String version2) {
int i=0,j=0;
long cur1,cur2;
while(i<version1.length()||j<version2.length()){
cur1=0;
cur2=0;
while(i<version1.length() && version1.charAt(i)!='.'){
cur1=cur1*10+(int)(version1.charAt(i)-'0');
i++;
}
while(j<version2.length() && version2.charAt(j)!='.'){
cur2=cur2*10+(int)(version2.charAt(j)-'0');
j++;
}
if(cur1<cur2){
return -1;
}else if(cur1>cur2){
return 1;
}else{
i++;
j++;
}
}
return 0;
}
}
三 树
BM23 二叉树的前序遍历
描述
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
数据范围:二叉树的节点数量满足1≤n≤100 ,二叉树节点的值满足1≤val≤100 ,树的各节点的值各不相同
思路:
堆栈:
根入栈-根出栈-右节点-左节点入栈-左节点-右节点出栈
根-左-右
public class Solution {
public int[] preorderTraversal (TreeNode root) {
ArrayList<Integer> res=new ArrayList<>();
if(root==null) return new int[0];
Stack<TreeNode> stack=new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node=stack.pop();
res.add(node.val);
if(node.right!=null) stack.push(node.right);
if(node.left!=null) stack.push(node.left);
}
int[] resArray=new int[res.size()];
for(int i=0;i<res.size();i++) resArray[i]=res.get(i);
return resArray;
}
}
BM24 二叉树的中序遍历
思路:
辅助栈和辅助指针
1 指针不为空就放指针,指针左下移动
2 栈不为空,就出栈,指针右下移动
public class Solution {
public int[] inorderTraversal (TreeNode root) {
if(root==null) return new int[0];
TreeNode cur=root;
Stack<TreeNode> stack=new Stack<>();
ArrayList<Integer> res=new ArrayList<>();
while(!stack.isEmpty()||cur!=null){
if(cur!=null){
stack.push(cur);
cur=cur.left;
}else{
cur=stack.pop();
res.add(cur.val);
cur=cur.right;
}
}
int[] resArray=new int[res.size()];
for(int i=0;i<res.size();i++) resArray[i]=res.get(i);
return resArray;
}
}
BM25 二叉树的后序遍历
思路:
辅助栈
1 放进入根
2 放进入左子树 右子树
3 拿出来放在数组前边
public class Solution {
public int[] postorderTraversal (TreeNode root) {
if(root==null) return new int[0];
Stack<TreeNode> stack=new Stack<>();
ArrayList<Integer> res=new ArrayList<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode temp=stack.pop();
res.add(0,temp.val);
if(temp.left!=null) stack.push(temp.left);
if(temp.right!=null) stack.push(temp.right);
}
int[] resTemp=new int[res.size()];
for(int i=0;i<res.size();i++) resTemp[i]=res.get(i);
return resTemp;
}
}
BM26 求二叉树的层序遍历
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/
public class Solution {
public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) {
ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();
if(root==null) return res;
Queue<TreeNode> stack=new LinkedList<>();
stack.offer(root);
while(!stack.isEmpty()){
int n=stack.size();
ArrayList<Integer> arrtemp=new ArrayList<>();
for(int i=0;i<n;i++){
TreeNode temp=stack.poll();
arrtemp.add(temp.val);
if(temp.left!=null) stack.offer(temp.left);
if(temp.right!=null) stack.offer(temp.right);
}
res.add(arrtemp);
}
return res;
}
}
BM27 按之字形顺序打印二叉树
import java.util.*;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();
if(pRoot==null) return res;
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(pRoot);
int index=1;
while(!queue.isEmpty()){
int n=queue.size();
LinkedList<Integer> list=new LinkedList<>();
for(int i=0;i<n;i++){
TreeNode temp=queue.poll();
if(index%2==1){
list.addLast(temp.val);
}else{
list.addFirst(temp.val);
}
if(temp.left!=null) queue.offer(temp.left);
if(temp.right!=null) queue.offer(temp.right);
}
ArrayList<Integer> l=new ArrayList<>(list);
res.add(l);
index++;
}
return res;
}
}
BM28 二叉树的最大深度
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/
public class Solution {
/**
* @param root TreeNode类
* @return int整型
*/
public int maxDepth (TreeNode root) {
// write code here
if(root==null) return 0;
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
}
BM29 二叉树中和为某一值的路径(一)
时间复杂度 O(n) 空间复杂度O(1)
import java.util.*;
public class Solution {
public boolean hasPathSum (TreeNode root, int sum) {
if(root==null) return false;
sum=sum-root.val;
if(root.left==null&&root.right==null&&sum==0) return true;
return hasPathSum(root.left,sum)||hasPathSum(root.right,sum);
}
}
BM30 二叉搜索树与双向链表
![image.png](https://img-blog.csdnimg.cn/img_convert/a21b485a2770a046fad934dc531cebf4.png#clientId=u26d4ada5-ba99-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=297&id=ud36040ec&margin=[object Object]&name=image.png&originHeight=297&originWidth=1120&originalType=binary&ratio=1&rotation=0&showTitle=false&size=75144&status=done&style=none&taskId=u3e24d23e-f658-496d-b7d8-18720549230&title=&width=1120)
数据范围:输入二叉树的节点数0≤n≤1000,二叉树中每个节点的值0≤val≤1000
要求:空间复杂度O(1)(即在原树上操作),时间复杂度O(n)
思路:二叉树中序遍历
1 递归写法
2 非递归写法
import java.util.*;
public class Solution {
TreeNode pre=null;
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree==null) return pRootOfTree;
recur(pRootOfTree);
while(pre.left!=null) pre=pre.left;
return pre;
}
public void recur(TreeNode pRootOfTree){
if(pRootOfTree==null) return;
recur(pRootOfTree.left);
if(pre!=null){
pre.right=pRootOfTree;
}
pRootOfTree.left=pre;
pre=pRootOfTree;
recur(pRootOfTree.right);
}
}
import java.util.*;
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree==null) return pRootOfTree;
Stack<TreeNode> stack=new Stack<>();
TreeNode cur=pRootOfTree;
TreeNode pre=null;
while(!stack.isEmpty()||cur!=null){
if(cur!=null){
stack.push(cur);
cur=cur.left;
}else{
cur=stack.pop();
if(pre!=null){
pre.right=cur;
}
cur.left=pre;
pre=cur;
cur=cur.right;
}
}
while(pre.left!=null) pre=pre.left;
return pre;
}
}
BM31 对称的二叉树
思路:
1 判断当前结点是否是一个结点
2 递归判断下面的结点是否是对称的
public class Solution {
boolean isSymmetrical(TreeNode pRoot) {
if(pRoot==null||(pRoot.left==null&&pRoot.right==null)) return true;
return isBalance(pRoot.left,pRoot.right);
}
public boolean isBalance(TreeNode left,TreeNode right){
if(left==null&&right==null) return true;
if((left!=null&&right==null)||(left==null&&right!=null)||(left.val!=right.val)) return false;
return isBalance(left.left,right.right)&&isBalance(left.right,right.left);
}
}
BM32 合并二叉树
思路:
递归调用
public class Solution {
public TreeNode mergeTrees (TreeNode t1, TreeNode t2) {
// write code here
if(t1==null&&t2!=null) return t2;
if(t1!=null&&t2==null) return t1;
if(t1==null&&t2==null) return null;
t1.val=t1.val+t2.val;
TreeNode left= mergeTrees (t1.left,t2.left);
TreeNode right=mergeTrees (t1.right,t2.right);
t1.left=left;
t1.right=right;
return t1;
}
}
BM33 二叉树的镜像
public class Solution {
public TreeNode Mirror (TreeNode pRoot) {
if(pRoot==null) return null;
TreeNode left=Mirror(pRoot.left);
TreeNode right=Mirror(pRoot.right);
pRoot.right=left;
pRoot.left=right;
return pRoot;
}
}
BM34 判断是不是二叉搜索树
1 递归写法
public class Solution {
static Integer nodevalue=null;
public boolean isValidBST (TreeNode root) {
if(root==null) return true;
boolean b1 = isValidBST (root.left);
if(nodevalue==null){
nodevalue=root.val;
}else{
if(root.val<nodevalue) return false;
nodevalue=root.val;
}
boolean b2 = isValidBST (root.right);
return b1&&b2;
}
}
2 非递归写法
public class Solution {
public boolean isValidBST (TreeNode root) {
if(root==null) return true;
Stack<TreeNode> stack=new Stack<>();
TreeNode cur=root;
TreeNode pre=null;
while(!stack.isEmpty()||cur!=null){
if(cur!=null){
stack.push(cur);
cur=cur.left;
}else{
cur=stack.pop();
if(pre!=null&&cur.val<pre.val) return false;
pre=cur;
cur=cur.right;
}
}
return true;
}
}
BM35 判断是不是完全二叉树
思路:
1 二叉树层序遍历
2 需要一个标志位,表示是否遇到结点不全的结点
public class Solution {
public boolean isCompleteTree(TreeNode root){
if(root == null) return true;
Deque<TreeNode> queue = new LinkedList<>();
queue.offer(root);
TreeNode left = null;
TreeNode right = null;
boolean flag = false; //标记是否遇到节点不双全的节点
while(!queue.isEmpty()){
root = queue.poll();
left = root.left;
right = root.right;
//遇到左右孩子不双全的节点并且该节点不是叶子节点的时候就不是完全二叉树
//左孩子为空并且右孩子不为空的时候不是完全二叉树
if((flag && !(left == null && right == null)) || (left == null && right != null)){
return false;
}
if(left != null) queue.offer(left);
if(right != null) queue.offer(right);
//标志着下一步是叶节点出现
if(left == null || right == null) flag = true;
}
return true;
}
}
BM36 判断是不是平衡二叉树
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
if(root==null||(root.left==null&&root.right==null)) return true;
int leftDepth=getTreeDepth(root.left);
int rightDepth=getTreeDepth(root.right);
if(Math.abs(leftDepth-rightDepth)>1) return false;
return IsBalanced_Solution(root.left)&&IsBalanced_Solution(root.right);
}
public int getTreeDepth(TreeNode root){
if(root==null) return 0;
return Math.max(getTreeDepth(root.left),getTreeDepth(root.right))+1;
}
}
BM37 二叉搜索树的最近公共祖先
思路:
利用二叉搜索树的性质, p < 目标结点 < q
public class Solution {
public int lowestCommonAncestor (TreeNode root, int p, int q) {
if(root==null) return -1;
return getNode(root,p,q).val;
}
public TreeNode getNode(TreeNode root,int p,int q){
if(q<p) return getNode(root,q,p);
Stack<TreeNode> stack=new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode temp=stack.pop();
if(temp.val>=p&&temp.val<=q){
return temp;
}else if(temp.val<q&&temp.val<p){
stack.push(temp.right);
}else{
stack.push(temp.left);
}
}
return new TreeNode(-1);
}
}
BM38 在二叉树中找到两个节点的最近公共祖先
思路:
比较当前结点是否和这些值相等,相等则直接返回
不相等,递归调用左子节点和右子节点
最后比较返回值
1 两者都不为null 返回root
2 其中一个为null 返回另一个
public class Solution {
public int lowestCommonAncestor (TreeNode root, int p, int q) {
if(root==null) return 0;
return getNode(root,p,q).val;
}
public TreeNode getNode(TreeNode root,int p,int q){
if(root==null||root.val==p||root.val==q) return root;
TreeNode left=getNode(root.left,p,q);
TreeNode right=getNode(root.right,p,q);
if(left==null) return right;
if(right==null) return left;
return root;
}
}
BM39 序列化二叉树
思路:
先序遍历
序列化:根左右
反序列化:根左右,用index标识数组的位置
public class Solution {
String Serialize(TreeNode root) {
StringBuilder sb=new StringBuilder();
if(root==null){
sb.append("#,");
return sb.toString();
}
sb.append(root.val+",");
sb.append(Serialize(root.left));
sb.append(Serialize(root.right));
return sb.toString();
}
int index=-1;
TreeNode Deserialize(String str) {
index++;
String[] strss=str.split(",");
TreeNode root=null;
if(!strss[index].equals("#")){
root=new TreeNode(Integer.parseInt(strss[index]));
root.left=Deserialize(str);
root.right=Deserialize(str);
}
return root;
}
}
BM40 重建二叉树
public class Solution {
HashMap<Integer,Integer> inOrder=new HashMap<Integer,Integer>();
int[] pre;
public TreeNode reConstructBinaryTree(int [] pre,int [] vin) {
this.pre=pre;
for(int i=0;i<vin.length;i++){
inOrder.put(vin[i],i);
}
return recur(0,0,vin.length-1);
}
public TreeNode recur(int rootIndex,int left,int right){
if(left>right) return null;
TreeNode root=new TreeNode(pre[rootIndex]);
root.left=recur(rootIndex+1 , left , inOrder.get(pre[rootIndex])-1 );
root.right=recur(rootIndex + inOrder.get(pre[rootIndex])-left+1, inOrder.get(pre[rootIndex])+1, right);
return root;
}
}
BM41 输出二叉树的右视图
思路:
1 现根据前序和中序恢复二叉树
2 层序遍历二叉树,把最后一个结点的数值加入到list中
import java.util.*;
public class Solution {
int[] pre;
HashMap<Integer,Integer> inOrder=new HashMap<Integer,Integer>();
public int[] solve (int[] xianxu, int[] zhongxu) {
this.pre=xianxu;
for(int i=0;i<zhongxu.length;i++){
inOrder.put(zhongxu[i],i);
}
TreeNode root=reBuild(0,0,zhongxu.length-1);
ArrayList<Integer> reslist=new ArrayList<>();
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int n=queue.size();
for(int i=0;i<n;i++){
TreeNode temp=queue.poll();
if(i==n-1) reslist.add(temp.val);
if(temp.left!=null) queue.offer(temp.left);
if(temp.right!=null) queue.offer(temp.right);
}
}
int[] res=new int[reslist.size()];
for(int i=0;i<res.length;i++){
res[i]=reslist.get(i);
}
return res;
}
public TreeNode reBuild(int rootIndex,int left,int right){
if(left>right) return null;
TreeNode root=new TreeNode(pre[rootIndex]);
root.left=reBuild(rootIndex+1,left,inOrder.get(pre[rootIndex])-1);
root.right=reBuild(rootIndex+inOrder.get(pre[rootIndex])-left+1,inOrder.get(pre[rootIndex])+1,right);
return root;
}
}
四、堆、栈、队列
BM42 用两个栈实现队列
思路:
分为两个栈,入栈和出栈
出栈空就从入栈转移过来
import java.util.Stack;
public class Solution {
//进栈
Stack<Integer> stack1 = new Stack<Integer>();
//出栈
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
return stack2.isEmpty()?-1:stack2.pop();
}
}
BM43 包含min函数的栈
双栈:
最小栈
普通栈
import java.util.Stack;
public class Solution {
Stack<Integer> min=new Stack<>();
Stack<Integer> stack=new Stack<>();
public void push(int node) {
stack.push(node);
if(min.isEmpty()){
min.push(node);
}else{
if(min.peek()>=node){
min.push(node);
}
}
}
public void pop() {
int n=stack.pop();
if(!min.isEmpty()&&min.peek()==n){
min.pop();
}
}
public int top() {
return stack.isEmpty()?-1:stack.peek();
}
public int min() {
return min.isEmpty()?-1:min.peek();
}
}
BM44 有效括号序列
import java.util.*;
public class Solution {
public boolean isValid (String s) {
if(s.equals("")||s.length()%2==1) return false;
Stack<Character> stack=new Stack<Character>();
char[] chars=s.toCharArray();
for(int i=0;i<chars.length;i++){
char c=chars[i];
if(c=='('||c=='['||c=='{'){
stack.push(c);
}else if(c==')'){
if(stack.isEmpty()) return false;
if(stack.peek()!='(') return false;
stack.pop();
}else if(c==']'){
if(stack.isEmpty()) return false;
if(stack.peek()!='[') return false;
stack.pop();
}else if(c=='}'){
if(stack.isEmpty()) return false;
if(stack.peek()!='{') return false;
stack.pop();
}
}
return stack.isEmpty();
}
}
BM45 滑动窗口的最大值
思路:
优先队列
import java.util.*;
public class Solution {
public ArrayList<Integer> maxInWindows(int [] num, int size) {
PriorityQueue<int[]> queue=new PriorityQueue<int[]>((a,b)->{
return a[0]==b[0]?a[1]-b[1]:b[0]-a[0];
});
ArrayList<Integer> list=new ArrayList<>();
if(size==0||num.length==0) return list;
for(int i=0;i<size-1;i++){
queue.offer(new int[]{num[i],i});
}
for(int i=size-1;i<num.length;i++){
queue.offer(new int[]{num[i],i});
while(queue.peek()[1]<=(i-size)){
queue.poll();
}
list.add(queue.peek()[0]);
}
return list;
}
}
BM46 最小的K个数
import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> res=new ArrayList<>();
if(k==0||input.length==0) return res;
Arrays.sort(input);
for(int i=0;i<k;i++) res.add(input[i]);
return res;
}
}
BM47 寻找第K大-快速排序思想
import java.util.*;
public class Solution {
public int findKth(int[] a, int n, int K) {
// write code here
quicksort(a,0,a.length-1);
return a[K-1];
}
public void quicksort(int[] a,int left,int right){
int l=left,r=right,povit=a[(left+right)/2];
while(l<r){
while(a[l]>povit) l++;
while(a[r]<povit) r--;
if(l>=r) break;
int temp=a[l];
a[l]=a[r];
a[r]=temp;
if(a[l]==povit) r--;
if(a[r]==povit) l++;
}
if(l==r){
r--;
l++;
}
if(l<right) quicksort(a,l,right);
if(left<r) quicksort(a,left,r);
}
}
BM48 数据流中的中位数
import java.util.*;
public class Solution {
PriorityQueue<Integer> post=new PriorityQueue<>();
PriorityQueue<Integer> pre=new PriorityQueue<>((a,b)->{
return b-a;
});
public void Insert(Integer num) {
if(pre.size()==post.size()){
if(pre.size()==0){
pre.offer(num);
}else{
if(num<pre.peek()){
pre.offer(num);
}else{
post.offer(num);
pre.offer(post.poll());
}
}
}else{
if(pre.peek()>num){
pre.offer(num);
post.offer(pre.poll());
}else{
post.offer(num);
}
}
}
public Double GetMedian() {
if(pre.size()==post.size()){
return pre.size()==0?0.0:(pre.peek()+post.peek())/2.0;
}else{
return pre.peek()+0.0;
}
}
}
NC137 表达式求值
描述
请写一个整数计算器,支持加减乘三种运算和括号。
数据范围:0≤∣s∣≤100,保证计算结果始终在整型范围内
要求:空间复杂度:O(n),时间复杂度O(n)
示例1
输入:"1+2"
返回值:3
示例2
输入:"(2*(3-4))*5"
返回值:-10
示例3
输入:"3+2*3*4-1"
返回值:26
思路:
双栈(为了避免负数开头,数字栈中压入0)
1 (和*直接入栈
2 数字获取整个数字入栈
3 +、-进行上一步的计算
4 )把最近的括号计算完
5 遍历完开始最后的合并计算,直到操作符栈为空
import java.util.Stack;
/**
* @Author 丁永新
* @Date 2022/1/23
*/
public class Solution {
public int solve (String s) {
s=s.replaceAll(" ","");//替换空格
char[] chars=s.toCharArray();
Stack<Integer> numops=new Stack<>();
numops.add(0);//防止出现负数为第一个数字
Stack<Character> signops=new Stack<>();
for(int i=0;i<chars.length;i++){
char c=chars[i];
if(c=='('||c=='*'||c=='/'){
signops.push(c);
}else if(isNum(c)){
String temp="";
while(i<chars.length&&isNum(chars[i])){
temp+=chars[i];
i++;
}
i--;
numops.push(Integer.parseInt(temp));
}else if(c=='+'||c=='-'){
while(!numops.isEmpty()&&!signops.isEmpty()
&&(signops.peek()=='+'||signops.peek()=='-'||signops.peek()=='*'||signops.peek()=='/')){
int n1=numops.pop();
int n2=numops.pop();
char sig=signops.pop();
numops.push(calculation(n2,n1,sig));
}
signops.push(c);
}else if(c==')'){
while(!numops.isEmpty()&&signops.peek()!='('){
int n1=numops.pop();
int n2=numops.pop();
char sig=signops.pop();
numops.push(calculation(n2,n1,sig));
}
signops.pop();
}
}
while(!signops.isEmpty()){
int n1=numops.pop();
int n2=numops.pop();
char sig=signops.pop();
numops.push(calculation(n2,n1,sig));
}
return numops.peek();
}
public int calculation(int a,int b,char sign){
switch(sign){
case '+':return a+b;
case '-':return a-b;
case '*':return a*b;
case '/':return (int)(a/b);
default :return 0;
}
}
public boolean isNum(char c){
if(c>='0'&&c<='9') return true;
return false;
}
}
五、哈希
BM50 两数之和
public class Solution {
public int[] twoSum (int[] numbers, int target) {
HashMap<Integer,Integer> map=new HashMap<Integer,Integer>();
for(int i=0;i<numbers.length;i++){
if(map.containsKey(target-numbers[i])){
return new int[]{map.get(target-numbers[i])+1,i+1};
}
map.put(numbers[i],i);
}
return new int[0];
}
}
BM51 数组中出现次数超过一半的数字
摩尔投票法:
两个数一样就相互抵消,如果某个数出现次数大于数组长度的一半,那抵消之后最后就剩这一个数字
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
int candidate=0;
int nums=0;
for(int i=0;i<array.length;i++){
if(nums==0){
candidate=array[i];
nums++;
}else{
if(candidate==array[i]){
nums++;
}else{
nums--;
}
}
}
if(nums!=0) return candidate;
return 0;
}
}
BM52 数组中只出现一次的两个数字
思路:分组异或
& 判断某一位上两个数字是否都是1
^ 去重 两个相同的数异或为0
public class Solution {
public int[] FindNumsAppearOnce (int[] array) {
int res=0;
for(int i:array) res^=i;
int bit=1;
while((bit&res)==0) bit=bit<<1;
int A=0,B=0;
for(int i:array){
if((i&bit)==0){
A^=i;
}else{
B^=i;
}
}
return A<B?new int[]{A,B}:new int[]{B,A};
}
}
BM53 缺失的第一个正整数
思路:
缺失的数据一定存在于1-n之间
新建1-n的数组,如果在范围内就给对应索引处赋值
返回数组中没有赋值的索引
时间复杂度:O(n) 空间复杂度:O(n)
import java.util.*;
public class Solution {
public int minNumberDisappeared (int[] nums) {
int[] res=new int[nums.length+1];
for(int i=0;i<nums.length;i++){
if(nums[i]>0&&nums[i]<=nums.length) res[nums[i]]=nums[i];
}
for(int i=1;i<res.length;i++){
if(res[i]==0) return i;
}
return res.length;
}
}
BM54 三数之和
思路:
for循环+双指针
import java.util.*;
public class Solution {
public ArrayList<ArrayList<Integer>> threeSum(int[] num) {
ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();
Arrays.sort(num);
for(int i=0;i<num.length-2;i++){
if(i>0 && num[i]==num[i-1]) continue;
int l=i+1,r=num.length-1;
while(l<r){
ArrayList<Integer> list=new ArrayList<>();
if(num[i]+num[l]+num[r]==0){
list.add(num[i]);
list.add(num[l]);
list.add(num[r]);
res.add(list);
while(l<r&&num[l]==num[++l]);
while(l<r&&num[r]==num[--r]);
}else if(num[i]+num[l]+num[r]<0){
l++;
}else if(num[i]+num[l]+num[r]>0){
r--;
}
}
}
return res;
}
}
六 递归、回溯
BM55 没有重复项数字的全排列
思路:
回溯,用一个boolean数组标识是否走过
import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();
public ArrayList<ArrayList<Integer>> permute(int[] num) {
if(num==null||num.length==0) return res;
boolean[] visited=new boolean[num.length];
ArrayList<Integer> list=new ArrayList<>();
backtrace(num,visited,list);
return res;
}
public void backtrace(int[] num,boolean[] visited,ArrayList<Integer> list){
if(list.size()==num.length){
res.add(new ArrayList<>(list));
return;
}
for(int i=0;i<num.length;i++){
if(!visited[i]){
list.add(num[i]);
visited[i]=true;
backtrace(num,visited,list);
list.remove(list.size()-1);
visited[i]=false;
}
}
}
}
BM56 有重复项数字的全排列
思路:
1 先求出全排列
2 treemap 自定义排序
3 遍历treemap把结果放入list数组
import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();
public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {
boolean[] visited=new boolean[num.length];
ArrayList<Integer> list=new ArrayList<>();
backtrace(num,visited,list);
TreeMap<String,ArrayList<Integer>> set=new TreeMap<String,ArrayList<Integer>>((a,b)->{
return a.compareTo(b);
});
for(int i=0;i<res.size();i++){
set.put(getString(res.get(i)),res.get(i));
}
res=new ArrayList<ArrayList<Integer>>();
for(Map.Entry<String,ArrayList<Integer>> map:set.entrySet()){
res.add(map.getValue());
}
return res;
}
public void backtrace(int[] num,boolean[] visited,ArrayList<Integer> list){
if(list.size()==num.length){
res.add(new ArrayList<>(list));
return;
}
for(int i=0;i<num.length;i++){
if(!visited[i]){
visited[i]=true;
list.add(num[i]);
backtrace(num,visited,list);
visited[i]=false;
list.remove(list.size()-1);
}
}
}
public String getString(ArrayList<Integer> list){
StringBuilder sb=new StringBuilder();
for(int i:list) {
sb.append(i+"");
}
return sb.toString();
}
}
BM57 岛屿数量
描述
给一个01矩阵,1代表是陆地,0代表海洋, 如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。
岛屿: 相邻陆地可以组成一个岛屿(相邻:上下左右) 判断岛屿个数。
例如:
[[1,1,0,0,0],
[0,1,0,1,1],
[0,0,0,1,1],
[0,0,0,0,0],
[0,0,1,1,1]]
对应的输出为 3
示例1
输入:[[1,1,0,0,0],[0,1,0,1,1],[0,0,0,1,1],[0,0,0,0,0],[0,0,1,1,1]]
返回值:3
示例2
输入:[[0]]
返回值:0
思路:
1 遍历数组
2 写一个改变状态的函数
3 一旦遇见为1的数组,就把和该位置相邻的所有的为1的位置改为2,同时岛屿数量加1
import java.util.*;
public class Solution {
/**
* 判断岛屿数量
* @param grid char字符型二维数组
* @return int整型
*/
int num=0;
public int solve (char[][] grid) {
// write code here
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[i].length;j++){
if(grid[i][j]=='1'){
num++;
changeState(grid,i,j);
}
}
}
return num;
}
public void changeState(char[][] grid,int i,int j){
if(i<0||i>=grid.length||j<0||j>=grid[0].length||grid[i][j]=='0') return;
if(grid[i][j]=='1'){
grid[i][j]='2';
changeState(grid,i,j+1);
changeState(grid,i,j-1);
changeState(grid,i+1,j);
changeState(grid,i-1,j);
}
}
}
BM58 字符串的排列
1 先求全排列
2 用hashset去重
3 把set放在list里面
import java.util.*;
public class Solution {
HashSet<String> set=new HashSet<>();
public ArrayList<String> Permutation(String str) {
ArrayList<String> res=new ArrayList<String>();
if(str==null||str.length()==0) return res;
boolean[] visited=new boolean[str.length()];
String s="";
backtrace(str,visited,s);
for(String j:set) res.add(j);
return res;
}
public void backtrace(String str,boolean[] visited,String s){
if(s.length()==str.length()) set.add(s);
for(int i=0;i<str.length();i++){
if(!visited[i]){
visited[i]=true;
s=s+str.charAt(i);
backtrace(str,visited,s);
s=s.substring(0,s.length()-1);
visited[i]=false;
}
}
}
}
BM59 N皇后问题
思路:
1 一行一行的判断
2 回溯
import java.util.*;
public class Solution {
int answer=0;
public int Nqueen (int n) {
// write code here
char[][] chess=new char[n][n];
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
chess[i][j]='.';
}
}
backtrace(chess,0);
return answer;
}
public void backtrace(char[][] chess , int row){
if(row==chess.length){
answer++;
return;
}
for(int i=0;i<chess.length;i++){
if(valid(chess,row,i)){
chess[row][i]='Q';
backtrace(chess,row+1);
chess[row][i]='.';
}
}
}
public boolean valid(char[][] chess,int i,int j){
if(chess[i][j]=='Q') return false;
for(int y=0;y<=i;y++){
if(chess[y][j]=='Q') return false;
}
for(int x=i,y=j;x>=0&&y>=0;x--,y--){
if(chess[x][y]=='Q') return false;
}
for(int x=i,y=j;x>=0&&y<chess.length;x--,y++){
if(chess[x][y]=='Q') return false;
}
return true;
}
}
BM60 括号生成
描述
给出n对括号,请编写一个函数来生成所有的由n对括号组成的合法组合。
例如,给出n=3,解集为:"((()))", "(()())", "(())()", "()()()", "()(())"
数据范围:0≤n≤10
要求:空间复杂度 O(n!),时间复杂度O(n!)
示例1
输入:1
返回值:["()"]
示例2
输入:2
返回值:["(())","()()"]
思路:
1 全排列
2 判断是否合法
3 去重
import java.util.*;
public class Solution {
HashSet<String> set=new HashSet<>();
public ArrayList<String> generateParenthesis (int n) {
ArrayList<String> res=new ArrayList<String>();
backtrace(n,n,"",n);
for(String s:set) res.add(s);
return res;
}
public void backtrace(int left,int right,String res,int n){
if(res.length()==2*n && judge(res)){
set.add(res);
return;
}
if(left!=0){
left--;
res=res+'(';
backtrace(left,right,res,n);
res=res.substring(0,res.length()-1);
left++;
}
if(right!=0){
right--;
res=res+')';
backtrace(left,right,res,n);
res=res.substring(0,res.length()-1);
right++;
}
}
public boolean judge(String str){
if(str==null||str.equals("")) return true;
if(str.length()%2==1) return false;
Stack<Character> stack=new Stack<>();
for(int i=0;i<str.length();i++){
char c=str.charAt(i);
if(c=='('){
stack.push(c);
}else{
if(stack.isEmpty()) return false;
if(!stack.isEmpty() && stack.peek()=='('){
stack.pop();
}
}
}
return stack.isEmpty();
}
}
BM61 矩阵最长递增路径
思路:
递归,用二维数组表示走过的地方
遍历每个位置,在每个位置上 上下左右走,遇到走过的地方直接返回结果
public class Solution {
int[][] dp;
public int solve (int[][] matrix) {
// write code here
if(matrix==null||matrix.length==0) {
return 0;
}
int result=0;
int row=matrix.length;
int collum=matrix[0].length;
dp=new int[row][collum];
for(int i=0;i<row;i++){
for(int j=0;j<collum;j++){
result=Math.max(result,dfs(matrix,i,j,Integer.MIN_VALUE));
}
}
return result;
}
public int dfs(int[][] matrix,int i,int j,int pre){
if(i<0||i>=matrix.length||j<0||j>=matrix[0].length) {
return 0;
}
if(matrix[i][j]<=pre) {
return 0;
}
if(dp[i][j]!=0) {
return dp[i][j];
}
int up=dfs(matrix,i-1,j,matrix[i][j]);
int down=dfs(matrix,i+1,j,matrix[i][j]);
int left=dfs(matrix,i,j-1,matrix[i][j]);
int right=dfs(matrix,i,j+1,matrix[i][j]);
dp[i][j]=Math.max(Math.max(up,down),Math.max(right,left))+1;
return dp[i][j];
}
}
七 动态规划
BM62 斐波那契数列
思路:
递归
非递归
public class Solution {
public int Fibonacci(int n) {
if(n<=2) return 1;
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
public class Solution {
public int Fibonacci(int n) {
if(n<=2) return 1;
int a=1,b=1,sum=a+b;
for(int i=3;i<=n;i++){
sum=a+b;
a=b;
b=sum;
}
return sum;
}
}
BM63 跳台阶
描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
数据范围:1≤n≤40
要求:时间复杂度:O(n) ,空间复杂度: O(1)
示例1
输入:2
返回值:2
说明:青蛙要跳上两级台阶有两种跳法,分别是:先跳一级,再跳一级或者直接跳两级。因此答案为2
示例2
输入:7
返回值:21
public class Solution {
public int jumpFloor(int target) {
if(target<=1) return 1;
int a=1,b=1,sum=a+b;
for(int i=2;i<=target;i++){
sum=a+b;
a=b;
b=sum;
}
return sum;
}
}
BM64 最小花费爬楼梯
描述
给定一个整数数组 cost ,其中 cost[i]是从楼梯第i个台阶向上爬需要支付的费用,下标从0开始。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
数据范围:数组长度满足 1≤n≤10^5,数组中的值满足 1≤cost≤10^4
示例1
输入:[2,5,20]
返回值:5
说明:你将从下标为1的台阶开始,支付5 ,向上爬两个台阶,到达楼梯顶部。总花费为5
示例2
输入:[1,100,1,1,1,90,1,1,80,1]
返回值:6
说明:
你将从下标为 0 的台阶开始。
1.支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
2.支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
3.支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
4.支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
5.支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
6.支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。
import java.util.*;
public class Solution {
public int minCostClimbingStairs (int[] cost) {
int n=cost.length;
if(n==1||n==2) return 0;
int[] dp=new int[n+1];
dp[0]=0;
dp[1]=0;
for(int i=2;i<=n;i++){
dp[i]=Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
return dp[n];
}
}
BM65 最长公共子序列(二)
描述
给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如果最长公共子序列为空,则返回"-1"。目前给出的数据,仅仅会存在一个最长的公共子序列
数据范围:0≤∣str1∣,∣str2∣≤2000
要求:空间复杂度 O(n^2),时间复杂度 O(n^2)
示例1
输入:"1A2C3D4B56","B1D23A456A"
返回值:"123456"
示例2
输入:"abc","def"
返回值:"-1"
示例3
输入:"abc","abc"
返回值:"abc"
示例4
输入:"ab",""
返回值:"-1"
import java.util.*;
public class Solution {
public String LCS (String s1, String s2) {
int n=s1.length();
int m=s2.length();
String[][] dp=new String[n+1][m+1];
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
if(i==0||j==0){
dp[i][j]="";
}else if(s1.charAt(i-1)==s2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1]+s1.charAt(i-1);
}else{
dp[i][j]=dp[i-1][j].length()>dp[i][j-1].length()?dp[i-1][j]:dp[i][j-1];
}
}
}
return dp[n][m]==""?"-1":dp[n][m];
}
}
BM66 最长公共子串
描述
给定两个字符串str1和str2,输出两个字符串的最长公共子串题目保证str1和str2的最长公共子串存在且唯一。
数据范围:1≤∣str1∣,∣str2∣≤5000
要求: 空间复杂度 O(n^2),时间复杂度 O(n^2)
示例1
输入:"1AB2345CD","12345EF"
返回值:"2345"
备注:1≤∣str1∣,∣str2∣≤5000
思路:
动态规划
某位置上相等: dp[i + 1][j + 1] = dp[i][j] + 1;
某位置上不等: dp[i + 1][j+1] = 0;
public String LCS(String str1, String str2) {
int maxLenth = 0;//记录最长公共子串的长度
//记录最长公共子串最后一个元素在字符串str1中的位置
int maxLastIndex = 0;
int[][] dp = new int[str1.length() + 1][str2.length() + 1];
for (int i = 0; i < str1.length(); i++) {
for (int j = 0; j < str2.length(); j++) {
//递推公式,两个字符相等的情况
if (str1.charAt(i) == str2.charAt(j)) {
dp[i + 1][j + 1] = dp[i][j] + 1;
//如果遇到了更长的子串,要更新,记录最长子串的长度,
//以及最长子串最后一个元素的位置
if (dp[i + 1][j + 1] > maxLenth) {
maxLenth = dp[i + 1][j+1];
maxLastIndex = i;
}
} else {
//递推公式,两个字符不相等的情况
dp[i + 1][j+1] = 0;
}
}
}
//最字符串进行截取,substring(a,b)中a和b分别表示截取的开始和结束位置
return str1.substring(maxLastIndex - maxLenth + 1, maxLastIndex + 1);
}
BM67 不同路径的数目(一)
描述
一个机器人在m×n大小的地图的左上角(起点)。机器人每次可以向下或向右移动。机器人要到达地图的右下角(终点)。可以有多少种不同的路径从起点走到终点?
备注:m和n小于等于100,并保证计算结果在int范围内 数据范围:0<n,m≤100,保证计算结果在32位整型范围内
要求:空间复杂度O(nm),时间复杂度O(nm)
进阶:空间复杂度O(1),时间复杂度O(min(n,m))
示例1
输入:2,1
返回值:1
示例2
输入:2,2
返回值:2
思路:
动态规划:dp[i][j]=dp[i-1][j]+dp[i][j-1];
每个位置只会从左边或者上边过来
import java.util.*;
public class Solution {
public int uniquePaths (int m, int n) {
int[][] dp=new int[m+1][n+1];
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(i==1||j==1){
dp[i][j]=1;
}else{
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
}
return dp[m][n];
}
}
BM68 矩阵的最小路径和
描述
给定一个 n * m 的矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和。
数据范围: 1≤n,m≤500,矩阵中任意值都满足0≤ai,j≤100
要求:时间复杂度 O(nm)
例如:当输入[[1,3,5,9],[8,1,3,4],[5,0,6,1],[8,8,4,0]]时,对应的返回值为12,
所选择的最小累加和路径如下图所示:
示例1
输入:[[1,3,5,9],[8,1,3,4],[5,0,6,1],[8,8,4,0]]
返回值:12
示例2
输入:[[1,2,3],[1,2,3]]
返回值:7
思路:
动态规划
1 上边只能从左边过来
2 左边只能从上边过来
3 其他的可以从上左两个方向过来
import java.util.*;
public class Solution {
public int minPathSum (int[][] matrix) {
if(matrix==null||matrix.length==0) return 0;
int n=matrix.length;
int m=matrix[0].length;
int[][] dp=new int[n+1][m+1];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i==1){
dp[i][j]=dp[i][j-1]+matrix[i-1][j-1];
}else if(j==1){
dp[i][j]=dp[i-1][j]+matrix[i-1][j-1];
}else{
dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+matrix[i-1][j-1];
}
}
}
return dp[n][m];
}
}
BM69 把数字翻译成字符串
描述
有一种将字母编码成数字的方式:'a'->1, 'b->2', ... , 'z->26'。
现在给一串数字,返回有多少种可能的译码结果
数据范围:字符串长度满足0<n≤90
进阶:空间复杂度O(n),时间复杂度O(n)
示例1
输入:"12"
返回值:2
说明:2种可能的译码结果(”ab” 或”l”)
示例2
输入:"31717126241541717"
返回值:192
说明:192种可能的译码结果
动态规划:
四种情况
1 无法翻译 当前为0 前面的>2或前面也为0
2 只能组合翻译 当前为0 前面为1或2
3 只能单独翻译 当前>6 前面为0或者前面>2
4 可以组合翻译 dp[i]=dp[i-1]+dp[i-2]
import java.util.*;
public class Solution {
public int solve (String nums) {
if(nums==null ||nums.length()==0) return 0;
int[] dp = new int[nums.length()+1];
dp[0]=1;
dp[1]=nums.charAt(0)=='0'?0:1;
for(int i=2;i<dp.length;i++){
//无法独立编码也无法组合编码
if(nums.charAt(i-1)=='0' && (nums.charAt(i-2)=='0' || nums.charAt(i-2)>'2')){
return 0;
//只能组合编码
}else if(nums.charAt(i-1)=='0'){
dp[i] = dp[i-2];
//只能独立编码
}else if(nums.charAt(i-2)=='0' || nums.charAt(i-2)>'2' || nums.charAt(i-2)=='2'&& nums.charAt(i-1)>'6' ){
dp[i] = dp[i-1];
//两种编码方式都可以
}else{
dp[i] = dp[i-1]+dp[i-2];
}
}
return dp[nums.length()];
}
}
BM70 兑换零钱(一)
描述
给定数组arr,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个aim,代表要找的钱数,求组成aim的最少货币数。 如果无解,请返回-1.
数据范围:数组大小满足0≤n≤10000 , 数组中每个数字都满足0<val≤10000,0≤aim≤5000
要求:时间复杂度O(n×aim) ,空间复杂度O(aim)
示例1
输入:[5,2,3],20
返回值:4
示例2
输入:[5,2,3],0
返回值:0
示例3
输入:[3,5],2
返回值:-1
思路:
动态规划
dp[i] = Math.min(dp[i],dp[i-arr[j]] + 1);
import java.util.*;
public class Solution {
public int minMoney (int[] arr, int aim) {
int Max = aim + 1;//定一个全局最大值
int []dp = new int[aim + 1];//dp[i]的含义是目标值为i的时候最少钱币数是多少。
Arrays.fill(dp,Max);//把dp数组全部定为最大值
dp[0] = 0;//总金额为0的时候所需钱币数一定是0
for(int i = 1;i <= aim;i ++){// 遍历目标值
for(int j = 0;j < arr.length;j ++){// 遍历钱币
if(arr[j] <= i){//如果当前的钱币比目标值小就可以兑换
dp[i] = Math.min(dp[i],dp[i-arr[j]] + 1);
}
}
}
return dp[aim] > aim ? -1 : dp[aim];
}
}
BM71 最长上升子序列(一)
描述
给定一个长度为 n 的数组 arr,求它的最长严格上升子序列的长度。
所谓子序列,指一个数组删掉一些数(也可以不删)之后,形成的新数组。例如 [1,5,3,7,3] 数组,其子序列有:[1,3,3]、[7] 等。但 [1,6]、[1,3,5] 则不是它的子序列。
我们定义一个序列是 严格上升 的,当且仅当该序列不存在两个下标 i 和 j 满足 i<j 且 arri≥arrj。
数据范围: 0≤n≤1000
要求:时间复杂度 O(n^2), 空间复杂度 O(n)
示例1
输入:[6,3,1,5,2,3,7]
返回值:4
说明:该数组最长上升子序列为 [1,2,3,7] ,长度为4
思路:
动态规划
遍历0到i-1位置的元素,如果有小于i位置的元素
则dp[i]=Math.max(dp[i],dp[j]+1);
返回最大值即可
import java.util.*;
public class Solution {
public int LIS (int[] arr) {
if(arr==null||arr.length==0) return 0;
int maxLen=1;
int[] dp=new int[arr.length];
Arrays.fill(dp,1);
for(int i=1;i<dp.length;i++){
for(int j=i-1;j>=0;j--){
if(arr[i]>arr[j]){
dp[i]=Math.max(dp[i],dp[j]+1);
}
}
maxLen=Math.max(maxLen,dp[i]);
}
return maxLen;
}
}
BM72 连续子数组的最大和
描述
输入一个长度为n的整型数组array,数组中的一个或连续多个整数组成一个子数组,子数组最小长度为1。求所有子数组的和的最大值。
数据范围:1<=n<=2*10^5 , −100<=a[i]<=100
要求:时间复杂度为 O(n),空间复杂度为 O(n)
进阶:时间复杂度为 O(n),空间复杂度为 O(1)
示例1
输入:[1,-2,3,10,-4,7,2,-5]
返回值:18
说明:经分析可知,输入数组的子数组[3,10,-4,7,2]可以求得最大和为18
示例2
输入:[2]
返回值:2
示例3
输入:[-10]
返回值:-10
思路1:
动态规划 dp[i]=array[i-1]+((dp[i-1]>0)?dp[i-1]:0);
时间复杂度O(n) 空间复杂度 O(n)
思路2:
思路1的改进,dp状态只需要1个,前一个元素的dp值
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
if(array==null || array.length==0) return 0;
int[] dp=new int[array.length+1];
int maxres=Integer.MIN_VALUE;
for(int i=1;i<=array.length;i++){
dp[i]=array[i-1]+((dp[i-1]>0)?dp[i-1]:0);
maxres=Math.max(maxres,dp[i]);
}
return maxres;
}
}
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
if(array==null || array.length==0) return 0;
int b=0;
int maxSum=Integer.MIN_VALUE;
for(int i=0;i<array.length;i++){
b=array[i]+(b>0?b:0);
maxSum=Math.max(b,maxSum);
}
return maxSum;
}
}
BM73 最长回文子串
描述
对于长度为n的一个字符串A(仅包含数字,大小写英文字母),请设计一个高效算法,计算其中最长回文子串的长度。数据范围:1≤n≤1000
要求:空间复杂度 O(1),时间复杂度 O(n^2)
进阶: 空间复杂度 O(n),时间复杂度 O(n)
示例1
输入:"ababc"
返回值:3
说明:最长的回文子串为"aba"与"bab",长度都为3
示例2
输入:"abbba"
返回值:5
示例3
输入:"b"
返回值:1
思路1:
中心扩散法,需要判断 双数和单数两种情况
时间复杂度:O(N^2),空间复杂度:O(1)
思路2:
双层for循环+双指针
时间复杂度:O(N^2),空间复杂度:O(1)
思路3:
动态规划
两层for循环
dp[left][right] = dp[left + 1][right - 1];
时间复杂度:O(N^2),空间复杂度:O(N^2)
import java.util.*;
public class Solution {
public int getLongestPalindrome(String A) {
char[] chars=A.toCharArray();
int maxLength=0;
for(int i=0;i<chars.length;i++){
maxLength=Math.max(maxLength,getLength(i,chars));
}
return maxLength;
}
public int getLength(int i,char[] chars){
//单数
int res=1,left=i-1,right=i+1;
while(left>=0 && right<chars.length){
if(chars[left]==chars[right]){
res+=2;
left--;
right++;
}else{
break;
}
}
//双数
int res0=0;
if((i+1)<chars.length && chars[i]==chars[i+1]){
left=i;
right=i+1;
while(left>=0 && right<chars.length){
if(chars[left]==chars[right]){
res0+=2;
left--;
right++;
}else{
break;
}
}
}
return Math.max(res,res0);
}
}
import java.util.*;
public class Solution {
public int getLongestPalindrome(String A) {
char[] chars=A.toCharArray();
int maxlength=0;
for(int i=0;i<chars.length;i++){
for(int j=i;j<chars.length;j++){
if(judge(i,j,chars)) maxlength=Math.max(maxlength,j-i+1);
}
}
return maxlength;
}
public boolean judge(int i,int j,char[] chars){
if(i==j) return true;
while(i<j){
if(chars[i]!=chars[j]) return false;
i++;
j--;
}
return true;
}
}
public int getLongestPalindrome(String A, int n) {
//边界条件判断
if (n < 2) return A.length();
//start表示最长回文串开始的位置,
//maxLen表示最长回文串的长度
int maxLen = 1;
boolean[][] dp = new boolean[n][n];
for (int right = 1; right < n; right++) {
for (int left = 0; left <= right; left++) {
//如果两种字符不相同,肯定不能构成回文子串
if (A.charAt(left) != A.charAt(right)) continue;
//下面是s.charAt(left)和s.charAt(right)两个
//字符相同情况下的判断
//如果只有一个字符,肯定是回文子串
if (right == left) {
dp[left][right] = true;
} else if (right - left <= 2) {
//类似于"aa"和"aba",也是回文子串
dp[left][right] = true;
} else {
//类似于"a******a",要判断他是否是回文子串,只需要
//判断"******"是否是回文子串即可
dp[left][right] = dp[left + 1][right - 1];
}
//如果字符串从left到right是回文子串,只需要保存最长的即可
if (dp[left][right] && right - left + 1 > maxLen) {
maxLen = right - left + 1;
}
}
}
//最长的回文子串
return maxLen;
}
BM74 数字字符串转化成IP地址
描述
现在有一个只包含数字的字符串,将该字符串转化成IP地址的形式,返回所有可能的情况。
例如:
给出的字符串为"25525522135",
返回["255.255.22.135", "255.255.221.35"]. (顺序没有关系)
数据范围:字符串长度 0≤n≤12 要求:空间复杂度O(n!),时间复杂度 O(n!)
注意:ip地址是由四段数字组成的数字序列,格式如 "x.x.x.x",其中 x 的范围应当是 [0,255]。
示例1
输入:"25525522135"
返回值:["255.255.22.135","255.255.221.35"]
示例2
输入:"1111"
返回值:["1.1.1.1"]
示例3
输入:"000256"
返回值:"[]"
思路1:
暴力枚举,每一个位包含一到三位字符
如果四个位置相加为s.length
思路2:
递归回溯
思路1:
import java.util.*;
public class Solution {
public ArrayList<String> restoreIpAddresses (String s) {
ArrayList<String> list = new ArrayList();
for(int a=1; a<4; a++){
for(int b=1; b<4; b++){
for(int c=1; c<4; c++){
for(int d=1; d<4; d++){
if(a+b+c+d==s.length()){
String s1 = s.substring(0, a);
String s2 = s.substring(a, a+b);
String s3 = s.substring(a+b, a+b+c);
String s4 = s.substring(a+b+c, a+b+c+d);
if(check(s1)&&check(s2)&&check(s3)&&check(s4)){
String ip = s1+"."+s2+"."+s3+"."+s4;
list.add(ip);
}
}
}
}
}
}
return list;
}
public boolean check(String s){
if(Integer.valueOf(s)<=255){
if(s.charAt(0)!='0' || s.charAt(0)=='0'&&s.length()==1)
return true;
}
return false;
}
}
思路2:
import java.util.*;
public class Solution {
public ArrayList<String> restoreIpAddresses (String s) {
if (s.length() > 12) {
return null;
}
StringBuilder sb = new StringBuilder(s);
doRestoreIpAddresses(sb, 1);
return ans;
}
ArrayList<String> ans = new ArrayList<>();
public void doRestoreIpAddresses(StringBuilder s, int m) { // 回溯暴力算法,还要很多地方没有剪枝
String[] nums = s.toString().split("\\.");
if (nums.length > 4) { // 剪枝
return;
}
if (validateIPv4(s.toString())) { // 结束条件
ans.add(String.copyValueOf(s.toString().toCharArray()));
return;
}
for (int i = m; i < s.length(); i++) { // 回溯
s.insert(i, ".");
doRestoreIpAddresses(s, i + 2);
s.replace(i, i + 1, "");
}
}
public boolean validateIPv4(String IP) {
String[] nums = IP.split("\\.");
if (nums.length != 4) {
return false;
}
for (String x : nums) {
// 0-255:
if (x.length() == 0 || x.length() > 3) return false;
// 0的情况
if (x.charAt(0) == '0' && x.length() != 1) return false;
// 大于255
if (Integer.parseInt(x) > 255) return false;
}
return true;
}
}
BM75 编辑距离(一)
描述
给定两个字符串 str1 和 str2 ,请你算出将 str1 转为 str2 的最少操作数。
你可以对字符串进行3种操作:
1.插入一个字符
2.删除一个字符
3.修改一个字符。
字符串长度满足1≤n≤1000,保证字符串中只出现小写英文字母。
示例1
输入:"nowcoder","new"
返回值:6
说明:"nowcoder"=>"newcoder"(将'o'替换为'e'),修改操作1次 "nowcoder"=>"new"(删除"coder"),删除操作5次
示例2
输入:"intention","execution"
返回值:5
说明:一种方案为:因为2个长度都是9,后面的4个后缀的长度都为"tion",于是从"inten"到"execu"逐个修改即可
示例3
输入:"now","nowcoder"
返回值:5
思路:
动态规划
int[][] dp=new int[str1.length()+1][str2.length()+1];
当前位置字符相等
dp[i+1][j+1]=dp[i][j];
当前位置字符不相等
dp[i+1][j+1]=Math.min(insert,delete,replace)
int delete=dp[i+1][j]+1;
int insert=dp[i][j+1]+1;
int change=dp[i][j]+1;
import java.util.*;
public class Solution {
public int editDistance (String str1, String str2) {
if(str1.length()==0||str2.length()==0) return str1.length()==0?str2.length():str1.length();
int[][] dp=new int[str1.length()+1][str2.length()+1];
//特殊解
for(int i=0;i<=str1.length();i++){
dp[i][0]=i;
}
for(int i=0;i<=str2.length();i++){
dp[0][i]=i;
}
for(int i=0;i<str1.length();i++){
for(int j=0;j<str2.length();j++){
if(str1.charAt(i)==str2.charAt(j)){
dp[i+1][j+1]=dp[i][j];
}else{
int delete=dp[i+1][j]+1;
int insert=dp[i][j+1]+1;
int change=dp[i][j]+1;
int min = change > insert ? insert : change;
min = delete > min ? min : delete;
dp[i + 1][j + 1] = min;
}
}
}
return dp[str1.length()][str2.length()];
}
}
BM76 正则表达式匹配
描述
请实现一个函数用来匹配包括'.'和'*'的正则表达式。
1.模式中的字符'.'表示任意一个字符
2.模式中的字符'*'表示它前面的字符可以出现任意次(包含0次)。
在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配
数据范围:
1.str 只包含从 a-z 的小写字母。
2.pattern 只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'。
3. 0≤str.length≤26
4. 0≤pattern.length≤26
示例1
输入:"aaa","a*a"
返回值:true
说明:中间的*可以出现任意次的a,所以可以出现1次a,能匹配上
示例2
输入:"aad","c*a*d"
返回值:true
说明:因为这里 c 为 0 个,a被重复一次, * 表示零个或多个a。因此可以匹配字符串 "aad"。
示例3
输入:"a",".*"
返回值:true
说明:".*" 表示可匹配零个或多个('*')任意字符('.')
示例4
输入:"aaab","a*a*a*c"
返回值:false
思路:
动态规划
1、 i和j处的字符相等 dp[i][j]=d[i-1][j-1]
2、 j处为'.' dp[i][j]=d[i-1][j-1]
3、 j处为'*' j-1处不为'.'
匹配成功
3.1 匹配0次 dp[i][j]=dp[i][j-2]
3.2 匹配1次 dp[i][j]=d[i][j-1]
3.3 匹配n次 d[i][j]=d[i-1][j]
匹配不成功
3.4 匹配0次 dp[i][j]=dp[i][j-2]
4、j-1处为'.' j处为'*'
4.1 匹配0次 dp[i][j]=dp[i][j-2]
4.2 匹配1次 dp[i][j]=d[i][j-1]
4.3 匹配n次 d[i][j]=d[i-1][j]
import java.util.*;
public class Solution {
public boolean match (String str, String pattern) {
boolean[][] dp=new boolean[str.length()+1][pattern.length()+1];
dp[0][0]=true;
for(int i=1;i<=pattern.length();i++){
//匹配0次
if (i >= 2 && pattern.charAt(i - 1) == '*') dp[0][i] = dp[0][i - 2];
}
for(int i=1;i<=str.length();i++){
for(int j=1;j<=pattern.length();j++){
//1 字符相等
if(str.charAt(i-1)==pattern.charAt(j-1)){
dp[i][j]=dp[i-1][j-1];
//2 j为'.'
}else if(pattern.charAt(j-1)=='.'){
dp[i][j]=dp[i-1][j-1];
//3 j为'*'
}else if(pattern.charAt(j-1)=='*'){
//3.1 j为'*' j-1为'.'
if(j>=2 && pattern.charAt(j-2)=='.'){
dp[i][j]=dp[i][j-2]||dp[i][j-1]||dp[i-1][j];
//3.2 j为'*' j-1不为'.'
}else{
//1 匹配成功
if(j>=2 && str.charAt(i-1)==pattern.charAt(j-2)){
dp[i][j]=dp[i][j-2]||dp[i][j-1]||dp[i-1][j];
//2 匹配不成功
}else{
dp[i][j]=dp[i][j-2];
}
}
}
}
}
return dp[str.length()][pattern.length()];
}
}
BM77 最长的括号子串
描述
给出一个长度为 n 的,仅包含字符 '(' 和 ')' 的字符串,计算最长的格式正确的括号子串的长度。
例1: 对于字符串 "(()" 来说,最长的格式正确的子串是 "()" ,长度为 2 .
例2:对于字符串 ")()())" , 来说, 最长的格式正确的子串是 "()()" ,长度为 4 .
字符串长度:0≤n≤5∗10 5
要求时间复杂度 O(n) ,空间复杂度 O(n).
示例1
输入:"(()"
返回值:2
示例2
输入:"(())"
返回值:4
思路:
动态规划
当前位置i为( dp为0
当前位置i为) 需要判断
1、i-1为( ,dp[i]=dp[i-2]+2
2、i-1不为(,i-dp[i-1]-1=='(' dp[i]=dp[i-dp[i-1]-2]+dp[i-1]+2
import java.util.*;
public class Solution {
public int longestValidParentheses (String s) {
int maxans = 0;
int[] dp = new int[s.length()];
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) == ')') {
if (s.charAt(i - 1) == '(') {
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
} else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
}
maxans = Math.max(maxans, dp[i]);
}
}
return maxans;
}
}
BM78 打家劫舍(一)
描述
你是一个经验丰富的小偷,准备偷沿街的一排房间,每个房间都存有一定的现金,为了防止被发现,你不能偷相邻的两家,即,如果偷了第一家,就不能再偷第二家;如果偷了第二家,那么就不能偷第一家和第三家。
给定一个整数数组nums,数组中的元素表示每个房间存有的现金数额,请你计算在不被发现的前提下最多的偷窃金额。
数据范围:数组长度满足1≤n≤2×10^5,数组中每个值满足1≤num[i]≤5000
示例1
输入:[1,2,3,4]
返回值:6
说明:最优方案是偷第 2,4 个房间
示例2
输入:[1,3,6]
返回值:7
说明:最优方案是偷第 1,3个房间
示例3
输入:[2,10,5]
返回值:10
说明:最优方案是偷第 2 个房间
思路:
动态规划
每天有两种状态 偷和不偷
偷 dp[i-1][1]+nums[i-1]; 左边不偷+今天
不偷 dp[i][1]=Math.max(dp[i-1][0],dp[i-1][1]); 昨天投和不偷的最大值
import java.util.*;
public class Solution {
public int rob (int[] nums) {
int[][] dp=new int[nums.length+1][2];
dp[0][0]=0;
dp[0][1]=0;
int max=0;
for(int i=1;i<=nums.length;i++){
dp[i][0]=dp[i-1][1]+nums[i-1];
dp[i][1]=Math.max(dp[i-1][0],dp[i-1][1]);
max=Math.max(dp[i][0],dp[i][1]);
}
return max;
}
}
BM79 打家劫舍(二)
描述
你是一个经验丰富的小偷,准备偷沿湖的一排房间,每个房间都存有一定的现金,为了防止被发现,你不能偷相邻的两家,即,如果偷了第一家,就不能再偷第二家,如果偷了第二家,那么就不能偷第一家和第三家。沿湖的房间组成一个闭合的圆形,即第一个房间和最后一个房间视为相邻。
给定一个长度为n的整数数组nums,数组中的元素表示每个房间存有的现金数额,请你计算在不被发现的前提下最多的偷窃金额。
数据范围:数组长度满足1≤n≤2×10^5,数组中每个值满足1≤nums[i]≤5000
示例1
输入:[1,2,3,4]
返回值:6
说明:最优方案是偷第 2 4 个房间
示例2
输入:[1,3,6]
返回值:6
说明:由于 1 和 3 是相邻的,因此最优方案是偷第 3 个房间
动态规划
1 从1-(n-1)
2 从2-n
计算两边动态规划
import java.util.*;
public class Solution {
public int rob (int[] nums) {
int[][] dp=new int[nums.length][2];
dp[0][0]=0;
dp[0][1]=0;
int RobFirst=0;
for(int i=1;i<nums.length;i++){
dp[i][0]=dp[i-1][1]+nums[i-1];
dp[i][1]=Math.max(dp[i-1][0],dp[i-1][1]);
RobFirst=Math.max(dp[i][0],dp[i][1]);
}
int noRobFirst=0;
dp=new int[nums.length][2];
for(int i=1;i<nums.length;i++){
dp[i][0]=dp[i-1][1]+nums[i];
dp[i][1]=Math.max(dp[i-1][0],dp[i-1][1]);
noRobFirst=Math.max(dp[i][0],dp[i][1]);
}
return Math.max(RobFirst,noRobFirst);
}
}
BM80 买卖股票的最好时机(一)
描述
假设你有一个数组prices,长度为n,其中prices[i]是股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
1.你可以买入一次股票和卖出一次股票,并非每天都可以买入或卖出一次,总共只能买入和卖出一次,且买入必须在卖出的前面的某一天
2.如果不能获取到任何利润,请返回0
3.假设买入卖出均无手续费
数据范围:0≤n≤10^5,0≤val≤10^4
要求:空间复杂度 O(1),时间复杂度 O(n)
示例1
输入:[8,9,2,5,4,7,1]
返回值:5
说明:在第3天(股票价格 = 2)的时候买入,在第6天(股票价格 = 7)的时候卖出,最大利润 = 7-2 = 5 ,不能选择在第2天买入,第3天卖出,这样就亏损7了;同时,你也不能在买入前卖出股票。
示例2
输入:[2,4,1]
返回值:2
示例3
输入:[3,2,1]
返回值:0
思路:
动态规划
今天持有=max(今天买入,昨天就持有)
今天不持有=max(昨天没有,昨天有今天卖出)
import java.util.*;
public class Solution {
public int maxProfit (int[] prices) {
int[][] dp=new int[prices.length][2];
dp[0][0]=0;
dp[0][1]=-prices[0];
for(int i=1;i<prices.length;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], -prices[i]);
}
return dp[prices.length-1][0];
}
}
//暴力解法
import java.util.*;
public class Solution {
public int maxProfit (int[] prices) {
if(prices.length<2) return 0;
int max=0;
for(int i=0;i<prices.length-1;i++){
int n1=prices[i];
for(int j=i+1;j<prices.length;j++){
max=Math.max(max,prices[j]-n1);
}
}
return max;
}
}
BM81 买卖股票的最好时机(二)
描述
假设你有一个数组prices,长度为n,其中prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
1. 你可以多次买卖该只股票,但是再次购买前必须卖出之前的股票
2. 如果不能获取收益,请返回0
3. 假设买入卖出均无手续费
数据范围:1≤n≤1×10^5,1≤prices[i]≤10^4
要求:空间复杂度 O(n),时间复杂度 O(n)
进阶:空间复杂度 O(1),时间复杂度 O(n)
示例1
输入:[8,9,2,5,4,7,1]
返回值:7
说明:
在第1天(股票价格=8)买入,第2天(股票价格=9)卖出,获利9-8=1
在第3天(股票价格=2)买入,第4天(股票价格=5)卖出,获利5-2=3
在第5天(股票价格=4)买入,第6天(股票价格=7)卖出,获利7-4=3
总获利1+3+3=7,返回7
示例2
输入:[5,4,3,2,1]
返回值:0
说明:由于每天股票都在跌,因此不进行任何交易最优。最大收益为0。
示例3
输入:[1,2,3,4,5]
返回值:4
说明:第一天买进,最后一天卖出最优。中间的当天买进当天卖出不影响最终结果。最大收益为4。
备注:总天数不大于200000。保证股票每一天的价格在[1,100]范围内。
思路:动态规划
今天持有=max(昨天利润+今天买入,昨天就持有)
今天不持有=max(昨天没有,昨天有今天卖出)
import java.util.*;
public class Solution {
public int maxProfit (int[] prices) {
int[][] dp=new int[prices.length][2];
dp[0][0]=0;
dp[0][1]=-prices[0];
for(int i=1;i<prices.length;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] -prices[i]);
}
return dp[prices.length-1][0];
}
}
BM82 买卖股票的最好时机(三)
描述
假设你有一个数组prices,长度为n,其中prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
1. 你最多可以对该股票有两笔交易操作,一笔交易代表着一次买入与一次卖出,但是再次购买前必须卖出之前的股票
2. 如果不能获取收益,请返回0
3. 假设买入卖出均无手续费
数据范围:1≤n≤10^5,股票的价格满足1≤val≤10^4
要求: 空间复杂度 O(n),时间复杂度 O(n)
进阶:空间复杂度 O(1),时间复杂度 O(n)
示例1
输入:[8,9,3,5,1,3]
返回值:4
说明:
第三天(股票价格=3)买进,第四天(股票价格=5)卖出,收益为2
第五天(股票价格=1)买进,第六天(股票价格=3)卖出,收益为2
总收益为4。
示例2
输入:[9,8,4,1]
返回值:0
示例3
输入:[1,2,8,3,8]
返回值:12
说明:第一笔股票交易在第一天买进,第三天卖出;第二笔股票交易在第四天买进,第五天卖出;总收益为12。
因最多只可以同时持有一只股票,所以不能在第一天进行第一笔股票交易的买进操作,又在第二天进行第二笔股票交易的买进操作(此时第一笔股票交易还没卖出),最后两笔股票交易同时在第三天卖出,也即以上操作不满足题目要求。
备注:总天数不大于200000。保证股票每一天的价格在[1,100]范围内。
思路:
5个状态:1)不操作2)第一次购买3)第一次卖出4)第二次购买5)第二次卖出
import java.util.*;
public class Solution {
public int maxProfit (int[] prices) {
// write code here
if (prices.length == 0) return 0;
/*
5个状态:1)不操作2)第一次购买3)第一次卖出4)第二次购买5)第二次卖出
dp[i][j]代表第i天状态为j时产生的最大收益
*/
int [][]dp = new int[prices.length][5];
//初始化
dp[0][1] = -prices[0];
dp[0][3] = -prices[0];
for (int i = 1; i<prices.length; i++) {
dp[i][0] = dp[i - 1][0];
//其中dp[i][1]有两个操作1)第i天没有操作2)第i天买入股票,所以此时最大收益,应该为这两个操作比大小
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
//其中dp[i][2]有两个操作1)第i天没有操作2)第i天卖出股票,所以此时最大收益,应该为这两个操作比大小
dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
//其中dp[i][3]有两个操作1)第i天没有操作2)第i天买入股票,所以此时最大收益,应该为这两个操作比大小
dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
//其中dp[i][4]有两个操作1)第i天没有操作2)第i天卖出股票,所以此时最大收益,应该为这两个操作比大小
dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
}
return dp[prices.length - 1][4];
}
}
八 字符串
BM83 字符串变形
描述
对于一个长度为 n 字符串,我们需要对它做一些变形。
首先这个字符串中包含着一些空格,就像"Hello World"一样,然后我们要做的是把这个字符串中由空格隔开的单词反序,同时反转每个字符的大小写。
比如"Hello World"变形后就变成了"wORLD hELLO"。
数据范围: 1≤n≤10^6, 字符串中包括大写英文字母、小写英文字母、空格。
进阶:空间复杂度 O(n), 时间复杂度 O(n)
输入描述:给定一个字符串s以及它的长度n(1 ≤ n ≤ 10^6)
返回值描述:请返回变形后的字符串。题目保证给定的字符串均由大小写字母和空格构成。
示例1
输入:"This is a sample",16
返回值:"SAMPLE A IS tHIS"
示例2
输入:"nowcoder",8
返回值:"NOWCODER"
思路:
1 用StringBuilder和Stack来做
2 遇到空格就把 StringBuilder的东西入栈并清空StringBuilder
3 遇到其他字符就放在StringBuilder中
4 最后把栈里的元素弹出来,后面拼空格
5 再把最后一个空格删掉
import java.util.*;
public class Solution {
public String trans(String s, int n) {
// write code here
//使用栈的特性,输出为反向
Stack<String>stack=new Stack<>();
StringBuffer buffer=new StringBuffer();
for(int i=0;i<n;i++){
//遇到空格入栈
if(s.charAt(i)==' '){
stack.push(buffer.toString());
buffer.delete(0,buffer.length());
}else{
//小写字母-32转为大写
if(s.charAt(i)>='a'&&s.charAt(i)<='z'){
buffer.append((char)(s.charAt(i)-32));
}else{
//大写字母+32转为小写
buffer.append((char)(s.charAt(i)+32));
}
}
}
//最后一个单词入栈
stack.push(buffer.toString());
StringBuffer ans=new StringBuffer();
while(!stack.isEmpty()){
ans.append(stack.pop());
//每个单词后接空格
ans.append(' ');
}
//删除最后一个多余空格
ans.deleteCharAt(ans.length()-1);
return ans.toString();
}
}
BM84 最长公共前缀
描述
给你一个大小为 n 的字符串数组 strs ,其中包含n个字符串 , 编写一个函数来查找字符串数组中的最长公共前缀,返回这个公共前缀。数据范围:0≤n≤5000, 0≤len(strs i)≤5000
进阶:空间复杂度 O(n),时间复杂度 O(n)
示例1
输入:["abca","abc","abca","abc","abcc"]
返回值:"abc"
示例2
输入:["abc"]
返回值:"abc"
思路:
1 先按照字典序排序
2 比较第一个和最后一个的公共前缀
import java.util.*;
public class Solution {
public String longestCommonPrefix (String[] strs) {
if(strs==null||strs.length==0) return "";
Arrays.sort(strs);
StringBuilder sb=new StringBuilder();
for(int i=0;i<strs[0].length();i++){
if(strs[0].charAt(i)==strs[strs.length-1].charAt(i)){
sb.append(strs[0].charAt(i));
}else{
break;
}
}
return sb.toString();
}
}
BM85 验证IP地址
描述
编写一个函数来验证输入的字符串是否是有效的 IPv4 或 IPv6 地址
IPv4 地址由十进制数和点来表示,每个地址包含4个十进制数,其范围为 0 - 255, 用(".")分割。比如,172.16.254.1;
同时,IPv4 地址内的数不会以 0 开头。比如,地址 172.16.254.01 是不合法的。
IPv6 地址由8组16进制的数字来表示,每组表示 16 比特。这些组数字通过 (":")分割。比如, 2001:0db8:85a3:0000:0000:8a2e:0370:7334 是一个有效的地址。而且,我们可以加入一些以 0 开头的数字,字母可以使用大写,也可以是小写。所以, 2001:db8:85a3:0:0:8A2E:0370:7334 也是一个有效的 IPv6 address地址 (即,忽略 0 开头,忽略大小写)。
然而,我们不能因为某个组的值为 0,而使用一个空的组,以至于出现 (::) 的情况。 比如, 2001:0db8:85a3::8A2E:0370:7334 是无效的 IPv6 地址。
同时,在 IPv6 地址中,多余的 0 也是不被允许的。比如, 02001:0db8:85a3:0000:0000:8a2e:0370:7334 是无效的。
说明: 你可以认为给定的字符串里没有空格或者其他特殊字符。
数据范围:字符串长度满足5≤n≤50
进阶:空间复杂度 O(n),时间复杂度 O(n)
示例1
输入:"172.16.254.1"
返回值:"IPv4"
说明:这是一个有效的 IPv4 地址, 所以返回 "IPv4"
示例2
输入:"2001:0db8:85a3:0:0:8A2E:0370:7334"
返回值:"IPv6"
说明:这是一个有效的 IPv6 地址, 所以返回 "IPv6"
示例3
输入:"256.256.256.256"
返回值:"Neither"
说明:这个地址既不是 IPv4 也不是 IPv6 地址
注意事项
2 分割字符串时,使用limit = -1的split函数,使得字符串末尾或开头有一个'.'或':'也能分割出空的字符串
2 使用Integer.parseInt()函数检查异常
3 startsWith("0")方法判断以"0"为开头
import java.util.*;
public class Solution {
public String solve(String IP) {
return validIPv4(IP) ? "IPv4" : (validIPv6(IP) ? "IPv6" : "Neither");
}
private boolean validIPv4(String IP) {
String[] strs = IP.split("\\.", -1);
if (strs.length != 4) {
return false;
}
for (String str : strs) {
if (str.length() > 1 && str.startsWith("0")) {
return false;
}
try {
int val = Integer.parseInt(str);
if (!(val >= 0 && val <= 255)) {
return false;
}
} catch (NumberFormatException numberFormatException) {
return false;
}
}
return true;
}
private boolean validIPv6(String IP) {
String[] strs = IP.split(":", -1);
if (strs.length != 8) {
return false;
}
for (String str : strs) {
if (str.length() > 4 || str.length() == 0) {
return false;
}
try {
int val = Integer.parseInt(str, 16);
} catch (NumberFormatException numberFormatException) {
return false;
}
}
return true;
}
}
BM86 大数加法
描述
以字符串的形式读入两个数字,编写一个函数计算它们的和,以字符串形式返回。数据范围:len(s),len(t)≤100000,字符串仅由'0'~‘9’构成
要求:时间复杂度 O(n)
示例1
输入:"1","99"
返回值:"100"
说明:1+99=100
示例2
输入:"114514",""
返回值:"114514"
思路:
合并链表的类似做法
1 反转两条字符串
2 合并字符串
3 反转结果
import java.util.*;
public class Solution {
public String solve (String s, String t) {
StringBuilder s0=new StringBuilder(s);
StringBuilder t0=new StringBuilder(t);
s=s0.reverse().toString();
t=t0.reverse().toString();
int add=0,i=0,j=0;
StringBuilder res=new StringBuilder();
while(i<s.length()&&j<t.length()){
int sum=(int)(s.charAt(i)-'0')+(int)(t.charAt(j)-'0')+add;
res.append(sum%10+"");
add=sum/10;
i++;
j++;
}
while(i<s.length()){
int sum=(int)(s.charAt(i)-'0')+add;
res.append(sum%10+"");
add=sum/10;
i++;
}
while(j<t.length()){
int sum=(int)(t.charAt(j)-'0')+add;
res.append(sum%10+"");
add=sum/10;
j++;
}
if(add!=0) res.append(add+"");
return res.reverse().toString();
}
}
九 双指针
BM87 合并两个有序的数组
描述
给出一个有序的整数数组 A 和有序的整数数组 B ,请将数组 B 合并到数组 A 中,变成一个有序的升序数组
数据范围:0≤n,m≤100,|A_i| <=100∣ |B_i| <= 100
注意:
1.保证 A 数组有足够的空间存放 B 数组的元素, A 和 B 中初始的元素数目分别为 m 和 n,A的数组空间大小为 m+n
2.不要返回合并的数组,将数组 B 的数据合并到 A 里面就好了,且后台会自动将合并后的数组 A 的内容打印出来,所以也不需要自己打印
3. A 数组在[0,m-1]的范围也是有序的
示例1
输入:[4,5,6],[1,2,3]
返回值:[1,2,3,4,5,6]
说明:A数组为[4,5,6],B数组为[1,2,3],后台程序会预先将A扩容为[4,5,6,0,0,0],B还是为[1,2,3],m=3,n=3,传入到函数merge里面,然后请同学完成merge函数,将B的数据合并A里面,最后后台程序输出A数组
示例2
输入:[1,2,3],[2,5,6]
返回值:[1,2,2,3,5,6]
思路:
1 双指针 合并数组 都放在A数组的末尾
import java.util.*;
public class Solution {
public void merge(int A[], int m, int B[], int n) {
int index=m+n-1,i=m-1,j=n-1;
while(i>=0&&j>=0){
if(A[i]>B[j]){
A[index]=A[i];
i--;
}else{
A[index]=B[j];
j--;
}
index--;
}
while(j>=0){
A[index]=B[j];
index--;
j--;
}
}
}
BM88 判断是否为回文字符串
思路1:
反转字符串比较
思路2:
双指针
import java.util.*;
public class Solution {
public boolean judge (String str) {
return str.equals(new StringBuilder(str).reverse().toString());
}
}
import java.util.*;
public class Solution {
public boolean judge (String str) {
int i=0,j=str.length()-1;
while(i<j){
if(str.charAt(i)!=str.charAt(j)) return false;
i++;
j--;
}
return true;
}
}
BM89 合并区间
描述
给出一组区间,请合并所有重叠的区间。请保证合并后的区间按区间起点升序排列。
数据范围:区间组数 0≤n≤2×10^5,区间内 的值都满足0^50≤val≤2×10^5
要求:空间复杂度 O(n),时间复杂度 O(nlogn)
进阶:空间复杂度 O(val),时间复杂度O(val)
示例1
输入:[[10,30],[20,60],[80,100],[150,180]]
返回值:[[10,60],[80,100],[150,180]]
示例2
输入:[[0,10],[10,20]]
返回值:[[0,20]]
思路:
1 按照区间第一个元素从小到大排序
2 遍历1到n-1个元素,判断是否可以合并区间
2.1 可以 设置i+1位置
2.2 不可以 添加进数组
时间复杂度:O(n) 空间复杂度:O(1)
import java.util.*;
/**
* Definition for an interval.
* public class Interval {
* int start;
* int end;
* Interval() { start = 0; end = 0; }
* Interval(int s, int e) { start = s; end = e; }
* }
*/
public class Solution {
public ArrayList<Interval> merge(ArrayList<Interval> intervals) {
ArrayList<Interval> res=new ArrayList<>();
if(intervals.size()<2) return intervals;
Collections.sort(intervals,(a,b)->a.start-b.start);
for(int i=0;i<intervals.size()-1;i++){
if(intervals.get(i).end>=intervals.get(i+1).start){
intervals.set(i+1,new Interval(intervals.get(i).start,Math.max(intervals.get(i+1).end,intervals.get(i).end)));
}else{
res.add(intervals.get(i));
}
}
res.add(intervals.get(intervals.size()-1));
return res;
}
}
BM90 最小覆盖子串
描述
给出两个字符串 s 和 t,要求在 s 中找出最短的包含 t 中所有字符的连续子串。
数据范围:0>∣S∣,∣T∣≤10000,保证s和t字符串中仅包含大小写英文字母
要求:进阶:空间复杂度 O(n) , 时间复杂度 O(n)
例如:
S ="XDOYEZODEYXNZ"S="XDOYEZODEYXNZ"
T ="XYZ"T="XYZ"
找出的最短子串为"YXNZ""YXNZ".
注意:
如果 s 中没有包含 t 中所有字符的子串,返回空字符串 “”;
满足条件的子串可能有很多,但是题目保证满足条件的最短的子串唯一。
示例1
输入:"XDOYEZODEYXNZ","XYZ"
返回值:"YXNZ"
示例2
输入:"abcAbA","AA"
返回值:"AbA"
思路:
左闭右开 滑动窗口
1 排除掉特殊条件
2 用一个字符数组 记录所需的字符和个数
3 声明左指针、右指针、命中need的数量、字符开始的指针、最小长度
4 开始扩大窗口
4.1 判断当前位置是否命中need
命中
如果have < need count++
have++
r++
缩小窗口
左指针未命中need 直接缩小
左指针命中need 且 have==need 此时count--
不命中
r++
continue
class Solution {
public String minWindow(String s, String t) {
if (s == null || s == "" || t == null || t == "" || s.length() < t.length()) {
return "";
}
//维护两个数组,记录已有字符串指定字符的出现次数,和目标字符串指定字符的出现次数
//ASCII表总长128
int[] need = new int[128];
int[] have = new int[128];
//将目标字符串指定字符的出现次数记录
for (int i = 0; i < t.length(); i++) {
need[t.charAt(i)]++;
}
//分别为左指针,右指针,最小长度(初始值为一定不可达到的长度)
//已有字符串中目标字符串指定字符的出现总频次以及最小覆盖子串在原字符串中的起始位置
int left = 0, right = 0, min = s.length() + 1, count = 0, start = 0;
while (right < s.length()) {
char r = s.charAt(right);
//说明该字符不被目标字符串需要,此时有两种情况
// 1.循环刚开始,那么直接移动右指针即可,不需要做多余判断
// 2.循环已经开始一段时间,此处又有两种情况
// 2.1 上一次条件不满足,已有字符串指定字符出现次数不满足目标字符串指定字符出现次数,那么此时
// 如果该字符还不被目标字符串需要,就不需要进行多余判断,右指针移动即可
// 2.2 左指针已经移动完毕,那么此时就相当于循环刚开始,同理直接移动右指针
if (need[r] == 0) {
right++;
continue;
}
//当且仅当已有字符串目标字符出现的次数小于目标字符串字符的出现次数时,count才会+1
//是为了后续能直接判断已有字符串是否已经包含了目标字符串的所有字符,不需要挨个比对字符出现的次数
if (have[r] < need[r]) {
count++;
}
//已有字符串中目标字符出现的次数+1
have[r]++;
//移动右指针
right++;
//当且仅当已有字符串已经包含了所有目标字符串的字符,且出现频次一定大于或等于指定频次
while (count == t.length()) {
//挡窗口的长度比已有的最短值小时,更改最小值,并记录起始位置
if (right - left < min) {
min = right - left;
start = left;
}
char l = s.charAt(left);
//如果左边即将要去掉的字符不被目标字符串需要,那么不需要多余判断,直接可以移动左指针
if (need[l] == 0) {
left++;
continue;
}
//如果左边即将要去掉的字符被目标字符串需要,且出现的频次正好等于指定频次,那么如果去掉了这个字符,
//就不满足覆盖子串的条件,此时要破坏循环条件跳出循环,即控制目标字符串指定字符的出现总频次(count)-1
if (have[l] == need[l]) {
count--;
}
//已有字符串中目标字符出现的次数-1
have[l]--;
//移动左指针
left++;
}
}
//如果最小长度还为初始值,说明没有符合条件的子串
if (min == s.length() + 1) {
return "";
}
//返回的为以记录的起始位置为起点,记录的最短长度为距离的指定字符串中截取的子串
return s.substring(start, start + min);
}
}
BM91 反转字符串
描述
写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串。(字符串长度不超过1000)
数据范围:0≤n≤1000
要求:空间复杂度 O(n),时间复杂度 O(n)
示例1
输入:"abcd"
返回值:"dcba"
示例2
输入:""
返回值:""
思路1:
调用API
思路2:
双指针
import java.util.*;
public class Solution {
public String solve (String str) {
return new StringBuilder(str).reverse().toString();
}
}
import java.util.*;
public class Solution {
public String solve (String str) {
char[] chars=str.toCharArray();
int l=0,r=str.length()-1;
while(l<r){
char c=chars[l];
chars[l]=chars[r];
chars[r]=c;
l++;
r--;
}
return new String(chars);
}
}
BM92 最长无重复子数组
描述
给定一个长度为n的数组arr,返回arr的最长无重复元素子数组的长度,无重复指的是所有数字都不相同。
子数组是连续的,比如[1,3,5,7,9]的子数组有[1,3],[3,5,7]等等,但是[1,3,7]不是子数组
数据范围:0≤arr.length≤10 5,0<arr[i]≤10^5
示例1
输入:[2,3,4,5]
返回值:4
说明:[2,3,4,5]是最长子数组
示例2
输入:[2,2,3,4,3]
返回值:3
说明:[2,3,4]是最长子数组
示例3
输入:[9]
返回值:1
示例4
输入:[1,2,3,1,2,3,2,2]
返回值:3
说明:最长子数组为[1,2,3]
示例5
输入:[2,2,3,4,8,99,3]
返回值:5
说明:最长子数组为[2,3,4,8,99]
思路:
滑动窗口,记录值和索引,重复的话直接从重复位置的下一个位置开始滑动
import java.util.*;
public class Solution {
public int maxLength (int[] arr) {
if(arr==null|arr.length==0) return 0;
if(arr.length==1) return 1;
HashMap<Integer,Integer> map=new HashMap<>();
int left=0,right=0,max=1;
while(right<arr.length){
int r=arr[right];
if(!map.containsKey(r)){
map.put(r,right);
}else{
int rindex=map.get(r);
left=Math.min(rindex+1,right);
map=new HashMap<>();
for(int i=left;i<=right;i++){
map.put(arr[i],i);
}
}
right++;
max=Math.max(right-left,max);
}
return max;
}
}
BM93 盛水最多的容器
描述
给定一个数组height,长度为n,每个数代表坐标轴中的一个点的高度,height[i]是在第i点的高度,请问,从中选2个高度与x轴组成的容器最多能容纳多少水
1.你不能倾斜容器
2.当n小于2时,视为不能形成容器,请返回0
3.数据保证能容纳最多的水不会超过整形范围,即不会超过231-1
数据范围: 0<=height.length<=10^5 0<=height[i]<=10^4
如输入的height为[1,7,3,2,4,5,8,2,7],那么如下图:
示例1
输入:[1,7,3,2,4,5,8,2,7]
返回值:49
示例2
输入:[2,2]
返回值:2
示例3
输入:[5,4,3,2,1,5]
返回值:25
思路:
双指针 每次移动最小的那根边
import java.util.*;
public class Solution {
public int maxArea (int[] height) {
if(height.length<=1) return 0;
int l=0,r=height.length-1;
int maxVolum=0;
while(l<r){
int minSide=Math.min(height[l],height[r]);
maxVolum=Math.max(maxVolum,minSide*(r-l));
if(height[l]==minSide){
l++;
}else{
r--;
}
}
return maxVolum;
}
}
BM94 接雨水问题
描述
给定一个整形数组arr,已知其中所有的值都是非负的,将这个数组看作一个柱子高度图,计算按此排列的柱子,下雨之后能接多少雨水。(数组以外的区域高度视为0)
数据范围:数组长度 0< n< 2*10^5,数组中每个值满足 0 < val <10^9,保证返回结果满足 0 < val < 10^9
要求:时间复杂度 O(n)
示例1
输入:[3,1,2,5,2,4]
返回值:5
说明:数组 [3,1,2,5,2,4] 表示柱子高度图,在这种情况下,可以接 5个单位的雨水,蓝色的为雨水 ,如题面图。
示例2
输入:[4,5,1,3,2]
返回值:2
思路1 :
双指针:
1 先找到最高的柱子
2 分别找左右两边能存水的值
左边: 当前位置的存水值=左侧最高值-当前值
右边: 当前位置的存水值=右侧最高值-当前值
思路2:
遍历每一层,高度为1、2、3...
左右不为空的中间夹层的个数
import java.util.*;
public class Solution {
public long maxWater (int[] arr) {
//寻找最高的位置
int maxHeight=0,maxIndex=0;
for(int i=0;i<arr.length;i++){
if(arr[i]>maxHeight){
maxIndex=i;
maxHeight=arr[i];
}
}
int left=0,right=0,maxleftindex=0,maxrightindex=arr.length-1;
//寻找左边能存的水
for(int i=0;i<maxIndex;i++){
if(i==0) continue;
if(arr[i]<arr[maxleftindex]){
left+=arr[maxleftindex]-arr[i];
}else{
maxleftindex=i;
}
}
//寻找右边能存的水
for(int i=arr.length-1;i>maxIndex;i--){
if(i==arr.length-1) continue;
if(arr[i]<arr[maxrightindex]){
right+=arr[maxrightindex]-arr[i];
}else{
maxrightindex=i;
}
}
return left+right;
}
}
十 贪心
BM95 分糖果问题
描述
一群孩子做游戏,现在请你根据游戏得分来发糖果,要求如下:
1. 每个孩子不管得分多少,起码分到一个糖果。
2. 任意两个相邻的孩子之间,得分较多的孩子必须拿多一些糖果。(若相同则无此限制)
给定一个数组 arr 代表得分数组,请返回最少需要多少糖果。
要求: 时间复杂度为 O(n) 空间复杂度为 O(n)
数据范围:1≤n≤100000 ,1≤ai≤1000
示例1
输入:[1,1,2]
返回值:4
说明:最优分配方案为1,1,2
示例2
输入:[1,1,1]
返回值:3
说明:最优分配方案是1,1,1
思路1:
所有孩子的糖果数初始化为 1;
先从左往右遍历一遍,如果右边孩子的评分比左边的高,则右边孩子的糖果数更新为左边孩子的 糖果数加 1;
再从右往左遍历一遍,如果左边孩子的评分比右边的高,且左边孩子当前的糖果数不大于右边孩子的糖果数,
则左边孩子的糖果数更新为右边孩子的糖果数加 1。
通过这两次遍历, 分配的糖果就可以满足题目要求了
时间复杂度:O(n) 空间复杂度:O(n)
思路2:
import java.util.*;
public class Solution {
public int candy (int[] arr) {
// write code here
int[] tmp= new int[arr.length];
Arrays.fill(tmp,1);
int count=0;
for(int i=1;i<arr.length;i++){
if(arr[i]>arr[i-1]){
tmp[i]=tmp[i-1]+1;
}
}
for(int i=arr.length-1;i>0;i--){
if(arr[i-1]>arr[i]){
tmp[i-1]=Math.max(tmp[i-1],tmp[i]+1);
}
}
for(int i:tmp) count+=i;
return count;
}
}
BM96 主持人调度
描述
有 n 个活动即将举办,每个活动都有开始时间与活动的结束时间,第 i 个活动的开始时间是 starti ,第 i 个活动的结束时间是 endi ,举办某个活动就需要为该活动准备一个活动主持人。
一位活动主持人在同一时间只能参与一个活动。并且活动主持人需要全程参与活动,换句话说,一个主持人参与了第 i 个活动,那么该主持人在 (starti,endi) 这个时间段不能参与其他任何活动。求为了成功举办这 n 个活动,最少需要多少名主持人。
数据范围: 1≤n≤10^5, -2^32<start_i,end_i<2^31-1
复杂度要求:时间复杂度O(nlogn) ,空间复杂度O(n)
示例1
输入:2,[[1,2],[2,3]]
返回值:1
说明:只需要一个主持人就能成功举办这两个活动
示例2
输入:2,[[1,3],[2,4]]
返回值:2
说明:需要两个主持人才能成功举办这两个活动
备注: 1≤n≤10^5 start_i,end_i在int范围内
思路:
区间排序 + 小顶堆
前面的主持人是否能抽出空,就看小顶堆第一个区间结束的时间是否小于等于当前活动的开始时间
import java.util.*;
public class Solution {
public int minmumNumberOfHost (int n, int[][] startEnd) {
Arrays.sort(startEnd, new Comparator<int[]>(){
@Override
public int compare(int[] task1, int[] task2){
if(task1[0] != task2[0]){
return task1[0] < task2[0]? -1: 1;
}else{
return task1[1] < task2[1]? -1: 1;
}
}
});
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(startEnd[0][1]);
for(int i = 1; i < n; i++){
if(startEnd[i][0] >= pq.peek()){
pq.poll();
}
pq.offer(startEnd[i][1]);
}
return pq.size();
}
}
十一 模拟
BM97 旋转数组
描述
一个数组A中存有 n 个整数,在不允许使用另外数组的前提下,将每个整数循环向右移 M( M >=0)个位置,即将A中的数据由(A0 A1 ……AN-1 )变换为(AN-M …… AN-1 A0 A1 ……AN-M-1 )(最后 M 个数循环移至最前面的 M 个位置)。如果需要考虑程序移动数据的次数尽量少,要如何设计移动的方法?
数据范围:0<n≤100,0≤m≤1000
进阶:空间复杂度 O(1),时间复杂度 O(n)
示例1
输入:6,2,[1,2,3,4,5,6]
返回值:[5,6,1,2,3,4]
示例2
输入:4,0,[1,2,3,4]
返回值:[1,2,3,4]
思路1:
旋转整体
旋转两个局部
思路2:
新建数组,每个元素移动m个位置
import java.util.*;
public class Solution {
public int[] solve (int n, int m, int[] a) {
// write code here
m=m%a.length;
reverse(0,a.length-1,a);
reverse(0,m-1,a);
reverse(m,a.length-1,a);
return a;
}
public void reverse(int n,int m,int[] a){
if(n>=m||n<0||m>a.length-1) return;
int l=n,r=m;
while(l<r){
int temp=a[l];
a[l]=a[r];
a[r]=temp;
r--;
l++;
}
}
}
BM98 螺旋矩阵
描述
给定一个m x n大小的矩阵(m行,n列),按螺旋的顺序返回矩阵中的所有元素。
数据范围:0≤n,m≤10,矩阵中任意元素都满足∣val∣≤100
要求:空间复杂度 O(nm) ,时间复杂度 O(nm)
示例1
输入:[[1,2,3],[4,5,6],[7,8,9]]
返回值:[1,2,3,6,9,8,7,4,5]
示例2
输入:[]
返回值:[]
思路:
定义四个顶点,逐渐缩小范围
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> spiralOrder(int[][] matrix) {
ArrayList<Integer> res=new ArrayList<Integer>();
if(matrix==null||matrix.length==0) return res;
int row=matrix.length,collum=matrix[0].length;
int top=0,left=0,down=row-1,right=collum-1;
while(left<=right&&top<=down){
for(int i=left;i<=right;i++){
res.add(matrix[top][i]);
}
for(int i=top+1;i<=down;i++){
res.add(matrix[i][right]);
}
if(left!=right&&top!=down){
for(int i=right-1;i>=left;i--){
res.add(matrix[down][i]);
}
for(int i=down-1;i>=top+1;i--){
res.add(matrix[i][left]);
}
}
left++;
right--;
top++;
down--;
}
return res;
}
}
BM99 顺时针旋转矩阵
描述
有一个NxN整数矩阵,请编写一个算法,将矩阵顺时针旋转90度。
给定一个NxN的矩阵,和矩阵的阶数N,请返回旋转后的NxN矩阵。
数据范围:0 < n < 300,矩阵中的值满足0≤val≤1000
要求:空间复杂度 O(N^2),时间复杂度 O(N^2)
进阶:空间复杂度 O(1),时间复杂度 O(N^2)
示例1
输入:[[1,2,3],[4,5,6],[7,8,9]],3
返回值:[[7,4,1],[8,5,2],[9,6,3]]
思路1 :
新建二维数组,依次把行放进列中
思路2 :
原地修改,左右左边互换,纵坐标=n-1-横坐标
思路3:
上下反转 + 对角线翻转
思路2:
import java.util.*;
public class Solution {
public int[][] rotateMatrix(int[][] mat, int n) {
int[][] temp = new int[n][n];//新建temp对象,作为最终返回的对象
for(int i = 0;i < n;i++){
for(int j = 0;j < n;j++){
temp[j][n-1-i] = mat[i][j];//直接交换
}
}
return temp;
}
}
思路3:
public int[][] rotateMatrix(int[][] matrix, int n) {
int length = matrix.length;
//先上下交换
for (int i = 0; i < length / 2; i++) {
int temp[] = matrix[i];
matrix[i] = matrix[length - i - 1];
matrix[length - i - 1] = temp;
}
//在按照对角线交换
for (int i = 0; i < length; ++i) {
for (int j = i + 1; j < length; ++j) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
return matrix;
}
BM100 设计LRU缓存结构
描述
设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为 k ,操作次数是 n ,并有如下两个功能
1. set(key, value):将记录(key, value)插入该结构
2. get(key):返回key对应的value值
提示:
1.某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的,然后都会刷新缓存。
2.当缓存的大小超过k时,移除最不经常使用的记录。
3.输入一个二维数组与k,二维数组每一维有2个或者3个数字,第1个数字为opt,第2,3个数字为key,value
若opt=1,接下来两个整数key, value,表示set(key, value)
若opt=2,接下来一个整数key,表示get(key),若key未出现过或已被移除,则返回-1
对于每个opt=2,输出一个答案
4.为了方便区分缓存里key与value,下面说明的缓存里key用""号包裹
要求:set和get操作复杂度均为 O(1)
数据范围: 1≤k≤n≤10^5,∣key∣,∣val∣≤2×10^9
示例1
输入:[[1,1,1],[1,2,2],[1,3,2],[2,1],[1,4,4],[2,2]],3
返回值:[1,-1]
说明:
[1,1,1],第一个1表示opt=1,要set(1,1),即将(1,1)插入缓存,缓存是{"1"=1}
[1,2,2],第一个1表示opt=1,要set(2,2),即将(2,2)插入缓存,缓存是{"1"=1,"2"=2}
[1,3,2],第一个1表示opt=1,要set(3,2),即将(3,2)插入缓存,缓存是{"1"=1,"2"=2,"3"=2}
[2,1],第一个2表示opt=2,要get(1),返回是[1],因为get(1)操作,缓存更新,缓存是{"2"=2,"3"=2,"1"=1}
[1,4,4],第一个1表示opt=1,要set(4,4),即将(4,4)插入缓存,但是缓存已经达到最大容量3,移除最不经常使用的{"2"=2},插入{"4"=4},缓存是{"3"=2,"1"=1,"4"=4}
[2,2],第一个2表示opt=2,要get(2),查找不到,返回是[1,-1]
示例2
输入:[[1,1,1],[1,2,2],[2,1],[1,3,3],[2,2],[1,4,4],[2,1],[2,3],[2,4]],2
返回值:[1,-1,-1,3,4]
思路1:
借助LinkedHashMap
思路2;
自己构建双链表,用hashmap 来快速定位,用双向链表来快速插入和删除
import java.util.*;
public class Solution {
public int[] LRU (int[][] operators, int k) {
LRUCache lru=new LRUCache(k);
ArrayList<Integer> res=new ArrayList<Integer>();
for(int i=0;i<operators.length;i++){
if(operators[i][0]==1){
lru.set(operators[i][1],operators[i][2]);
}else if(operators[i][0]==2){
int temp= lru.get(operators[i][1]);
res.add(temp);
}
}
int[] r=new int[res.size()];
for(int i=0;i<res.size();i++){
r[i]=res.get(i);
}
return r;
}
class LRUCache{
LinkedHashMap<Integer,Integer> cache;
int SIZE;
public LRUCache(int k){
this.SIZE=k;
this.cache=new LinkedHashMap<Integer,Integer>();
}
public void set(int key,int value){
if(cache.containsKey(key)){
makeRecent(key);
}else if(cache.size()==SIZE){
int tkey=cache.entrySet().iterator().next().getKey();
cache.remove(tkey);
}
cache.put(key,value);
}
public int get(int key){
if(cache.containsKey(key)){
makeRecent(key);
return cache.get(key);
}else{
return -1;
}
}
public void makeRecent(int key){
int value=cache.get(key);
cache.remove(key);
cache.put(key,value);
}
}
}
import java.util.*;
class LRUCache {
class Node {
int k, v;
Node l, r;
Node(int _k, int _v) {
k = _k;
v = _v;
}
}
int n;
Node head, tail;
Map<Integer, Node> map;
public LRUCache(int capacity) {
n = capacity;
map = new HashMap<>();
head = new Node(-1, -1);
tail = new Node(-1, -1);
head.r = tail;
tail.l = head;
}
public int get(int key) {
if (map.containsKey(key)) {
Node node = map.get(key);
refresh(node);
return node.v;
}
return -1;
}
public void put(int key, int value) {
Node node = null;
if (map.containsKey(key)) {
node = map.get(key);
node.v = value;
} else {
if (map.size() == n) {
Node del = tail.l;
map.remove(del.k);
delete(del);
}
node = new Node(key, value);
map.put(key, node);
}
refresh(node);
}
// refresh 操作分两步:
// 1. 先将当前节点从双向链表中删除(如果该节点本身存在于双向链表中的话)
// 2. 将当前节点添加到双向链表头部
void refresh(Node node) {
delete(node);
node.r = head.r;
node.l = head;
head.r.l = node;
head.r = node;
}
// delete 操作:将当前节点从双向链表中移除
// 由于我们预先建立 head 和 tail 两位哨兵,因此如果 node.l 不为空,则代表了 node 本身存在于双向链表(不是新节点)
void delete(Node node) {
if (node.l != null) {
Node left = node.l;
left.r = node.r;
node.r.l = left;
}
}
}
public class Solution {
/**
* lru design
* @param operators int整型二维数组 the ops
* @param k int整型 the k
* @return int整型一维数组
*/
public int[] LRU (int[][] operators, int k) {
List<Integer> list = new ArrayList<>();
LRUCache lru = new LRUCache(k);
for (int[] op : operators) {
int type = op[0];
if (type == 1) {
// set(k,v) 操作
lru.put(op[1], op[2]);
} else {
// get(k) 操作
list.add(lru.get(op[1]));
}
}
int n = list.size();
int[] ans = new int[n];
for (int i = 0; i < n; i++) ans[i] = list.get(i);
return ans;
}
}
import java.util.*;
class LRUcache{
class Node {
int k,v;
Node l,r;
public Node(int k,int v){
this.k=k;
this.v=v;
}
}
HashMap<Integer,Node> map=new HashMap<Integer,Node>();
int size=0;
Node head,tail;
public LRUcache(int size){
this.size=size;
head=new Node(-1,-1);
tail=new Node(-1,-1);
head.r=tail;
tail.l=head;
}
public int get(int k){
if(map.containsKey(k)){
makeRecently(k);
return map.get(k).v;
}
return -1;
}
public void put(int k,int v){
if(map.containsKey(k)){
makeRecently(k);
map.get(k).v=v;
}else{
if(map.size()==size){
int deletek=tail.l.k;
deleteNode(deletek);
}
addHead(k,v);
}
}
public void addHead(int k,int v){
Node putNode=new Node(k,v);
Node tempput= head.r;
head.r=putNode;
putNode.l=head;
putNode.r=tempput;
tempput.l=putNode;
map.put(k,putNode);
}
public void makeRecently(int k){
if(map.containsKey(k)){
Node tempdelete=map.get(k);
deleteNode(k);
addHead(k,tempdelete.v);
}
}
public void deleteNode(int k){
Node tempdelete=map.get(k);
tempdelete.l.r=tempdelete.r;
tempdelete.r.l=tempdelete.l;
map.remove(k);
}
}
public class Solution {
public int[] LRU (int[][] operators, int k) {
LRUcache lru=new LRUcache(k);
ArrayList<Integer> res=new ArrayList<Integer>();
for(int i=0;i<operators.length;i++){
if(operators[i][0]==1){
lru.put(operators[i][1],operators[i][2]);
}else if(operators[i][0]==2){
int temp= lru.get(operators[i][1]);
res.add(temp);
}
}
int[] r=new int[res.size()];
for(int i=0;i<res.size();i++){
r[i]=res.get(i);
}
return r;
}
}
BM101 设计LFU缓存结构
描述
一个缓存结构需要实现如下功能。
set(key, value):将记录(key, value)插入该结构
get(key):返回key对应的value值
但是缓存结构中最多放K条记录,如果新的第K+1条记录要加入,就需要根据策略删掉一条记录,然后才能把新记录加入。这个策略为:在缓存结构的K条记录中,哪一个key从进入缓存结构的时刻开始,被调用set或者get的次数最少,就删掉这个key的记录;
如果调用次数最少的key有多个,上次调用发生最早的key被删除
这就是LFU缓存替换算法。实现这个结构,K作为参数给出
数据范围:0<k≤10^5,∣val∣≤2×10^9
要求:get和set的时间复杂度都是O(logn),空间复杂度是O(n)
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,返回一个答案
示例1
输入:[[1,1,1],[1,2,2],[1,3,2],[1,2,4],[1,3,5],[2,2],[1,4,4],[2,1]],3
返回值:[4,-1]
说明:在执行"1 4 4"后,"1 1 1"被删除。因此第二次询问的答案为-1
备注: 1≤k≤n≤10^5 ; −2×10^9≤x,y≤2×10^9
思路1:
需要维持一个稳定排序的列表,get和set次数最少的先排除,如果get和set次数一致,先移除最老的那个
LinkedHashMap
import java.util.*;
public class Solution {
public int[] LFU (int[][] operators, int k) {
// write code here
LFUCache cache=new LFUCache(k);
ArrayList<Integer> res=new ArrayList<>();
for(int i=0;i<operators.length;i++){
if(operators[i][0]==1){
cache.put(operators[i][1],operators[i][2]);
}else{
int n=cache.get(operators[i][1]);
res.add(n);
}
}
int[] resArray=new int[res.size()];
for(int i=0;i<res.size();i++){
resArray[i]=res.get(i);
}
return resArray;
}
}
class LFUCache{
int size=0;
LinkedHashMap<Integer,Node> map;
public LFUCache(int k){
this.size=k;
map=new LinkedHashMap<>();
}
public void put(int k,int v){
if(map.containsKey(k)){
makeRecently(k);
Node node=map.get(k);
node.value=v;
map.put(k,node);
}else{
if(map.size()>=this.size){
removeMin();
}
Node nodep=new Node(v,1);
map.put(k,nodep);
}
}
public int get(int k){
if(map.containsKey(k)){
makeRecently(k);
Node tempnode=map.get(k);
return tempnode.value;
}
return -1;
}
public void makeRecently(int k){
if(map.containsKey(k)){
Node node=map.get(k);
node.opsnum++;
map.remove(k);
map.put(k,node);
}
}
public void removeMin(){
int min=Integer.MAX_VALUE;
int index=-1;
for(Map.Entry<Integer,Node> m:map.entrySet()){
if(m.getValue().opsnum < min){
index=m.getKey();
min=m.getValue().opsnum;
}
}
if(min!=Integer.MAX_VALUE) {
map.remove(index);
}
}
}
class Node{
int value;
int opsnum;
public Node(int v,int opsnum){
this.value=v;
this.opsnum=opsnum;
}
}