剑指offer刷题


因为力扣没有剑指了,所以只能去牛客刷了,我草

PS:有的和力扣烧鸡100重复了,跳过

链表部分

从尾到头打印链表

public class Solution {
    ArrayList<Integer> list=new ArrayList<Integer>();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ListNode p=listNode;
        if(p!=null)
        {
            printListFromTailToHead(p.next);
            list.add(p.val);
        }
        return list;
    }
}

一开始把list写函数里面了,大错特错

反转链表

public ListNode ReverseList (ListNode head) {
    // write code here
    ListNode l=null;
    ListNode m=head;
    while(m!=null)
    {
       ListNode r=m.next;
        m.next=l;
        l=m;
        m=r;
    }
    return l;
}

一开始return的m,后来发现m都null了,return锤子

合并两个排序的链表

这题比较简单,弱智思路一个一个比就行了,然后最后把剩下的全加上去

public ListNode Merge (ListNode pHead1, ListNode pHead2) {
    // write code here
    ListNode dummy=new ListNode(0);
    ListNode p=dummy;
    while(pHead1!=null&&pHead2!=null)
    {
        if(pHead1.val<=pHead2.val)
        {
            p.next=pHead1;
            pHead1=pHead1.next;
        }else
        {
            p.next=pHead2;
            pHead2=pHead2.next;
        }
        p=p.next;
    }
    while(pHead1!=null)
    {
        p.next=pHead1;
        p=p.next;
        pHead1=pHead1.next;
    }

    while(pHead2!=null)
    {
        p.next=pHead2;
        p=p.next;
        pHead2=pHead2.next;
    }
    return dummy.next;
}

两个链表的第一个公共结点

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
    ListNode p1=pHead1;ListNode p2=pHead2;
    int lenp1=0;
    while(p1!=null)
    {
        p1=p1.next;
        lenp1++;
    }
    int lenp2=0;
    while(p2!=null)
    {
        p2=p2.next;
        lenp2++;
    }
    p1=pHead1;p2=pHead2;
   if(lenp1>lenp2)
   {
    int k=lenp1-lenp2;
        while(k>0)
        {
            p1=p1.next;
            k--;
        }
   }else 
   {
    int k=lenp2-lenp1;
    while(k>0)
    {
        p2=p2.next;
        k--;
    }
   }
   while(p1!=p2)
   {
    p1=p1.next;
    p2=p2.next;
   }
   return p1;
    
}

长的先走

链表中倒数最后k个结点

public ListNode FindKthToTail (ListNode pHead, int k) {
    // write code here
    if(pHead==null)return null;
    ListNode p=pHead;
    ListNode q=pHead;
    int len=0;
    ListNode m=pHead;
    while(m!=null)
    {   
        len++;
        m=m.next;
    }
    if(len<k)return null;
    while(k>0)
    {
        q=q.next;
        k--;
    }
    while(q!=null)
    {
        q=q.next;
        p=p.next;
    }
    return p;
}

老生常谈的题目了,注意pHead为空的情况

链表中环的入口结点

反正有个公式,背吧懒得推导

总的来说就是

1.先判断与有无环,套路

2.把fast放到head结点,和slow一起一个一个走,相遇为入口节点

为什么呢,不知道

public ListNode EntryNodeOfLoop(ListNode pHead) {
    if(pHead==null)
        return null;
    ListNode slow=pHead;
    ListNode fast=pHead;
    while(fast!=null&&fast.next!=null)
    {
        fast=fast.next.next;
        slow=slow.next;
        if(slow==fast)
            break;
    }
    if(fast==null||fast.next==null)return null;
    fast=pHead;
    // 与第一次相遇的结点相同速度出发,相遇结点为入口结点
    while(fast!=slow)
    {
        fast=fast.next;
        slow=slow.next;
    }
    return fast;
}

删除链表的节点

public ListNode deleteNode (ListNode head, int val) {
    // write code here
    if(head==null)return null;
    ListNode p=head;
    if(p.val==val)
    {
        return head.next;
    }
    while(p.next!=null)
    {
        if(p.next.val==val)//注意,这里直接把p当pre用就行了,也就是说判断P.next是不是val即可
        {
            ListNode q=p.next;
            p.next=q.next;
            q.next=null;
            return head;
        }
        p=p.next;
    }
    return null;
}

注意第一个节点

删除链表中重复的结点

public ListNode deleteDuplication(ListNode head) {
     ListNode dummy=new ListNode(-1);
     ListNode tail=dummy;
     while(head!=null)
     {
         if(head.next==null||head.next.val!=head.val)
         {
             tail.next=head;
             tail=head;
         }
         while(head.next!=null&&head.next.val==head.val)head=head.next;
         head=head.next;
     }
     tail.next=null; //这一步至关重要,比如12233455,如果最后不断掉,那么会返回1455
     return dummy.next;
 }

我一开始的傻逼想法:用set,false了就删掉,后来发现这样能保留第一个重复的节点,我是傻逼

二叉树的深度

经典递归,反正等于子树高度加一

public int TreeDepth(TreeNode root) {
    if(root==null)return 0;//这一句一写还用考虑left,right=null吗?
    else
    return Math.max(TreeDepth(root.left),TreeDepth(root.right))+1;
}

按之字形顺序打印二叉树

注意点:

1.如何判断一层:队列size

2.如何实现”之“,直接用Collections.reverse(),就不用麻烦的后端插入Arraylist和前端插入Arraylist了

public ArrayList<ArrayList<Integer>> Print (TreeNode pRoot) {
    // 思路:双端队列,奇数层翻转(从0开始),偶数层不翻转
    ArrayList<ArrayList<Integer>> res=new ArrayList<>();
    if(pRoot==null)return res;
    Deque <TreeNode>dq=new LinkedList<TreeNode>();
    boolean flag=true;
    dq.offer(pRoot);
    while(!dq.isEmpty())
    {
        ArrayList<Integer> aa=new ArrayList<>();
        int n=dq.size();//这一层有多少node
        flag=!flag;
        for(int i=0;i<n;i++)
        {
            TreeNode p=dq.pollFirst();
            aa.add(p.val);
            if(p.left!=null)
                dq.offer(p.left);
            if(p.right!=null)
                dq.offer(p.right);
        }
        if(flag)
        {
            Collections.reverse(aa);
        } 
        res.add(aa);        

    }
    return res;
}

二叉搜索树的第k个节点

 ArrayList<Integer>res=new ArrayList<>();
public int KthNode (TreeNode proot, int k) {
    if(proot==null||k==0)return -1;//空,或者没k
    mid(proot);//中序遍历获得升序数组
    if(k>res.size())return -1;//没这么多node
    return res.get(k-1);
}

public void mid(TreeNode root)
{
    if(root==null)return;
    mid(root.left);
    res.add(root.val);
    mid(root.right);
}

知识点:二叉搜索树中序遍历为升序序列

重建二叉树

这题力扣烧鸡100也有,之前就稀里糊涂过去了

额其实842期末也有,但当时也是稀里糊涂过去了

注意点放注释里了

这个是力扣是上我写的,傻逼牛客有问题,算了

public TreeNode buildTree(int[] preorder, int[] inorder) {
    return buildTreeHelper(preorder,0,preorder.length,inorder,0,inorder.length);
}
public TreeNode buildTreeHelper(int[]preorder,int pstart,int pend,int []inorder,int istart,int iend)
{   
    //pstart:前序数组中左子树的开头,pend:前序数组中左子树的结束,左闭右开
    //istart:终须数组中左子树的开头,iend:终须数组中左子树的结束,左闭右开
    if(pstart==pend)
        return null;
    int root_val=preorder[pstart];//前序数组第一个为更节点
    TreeNode root=new TreeNode(root_val);
    int iroot_index=0;//中序遍历中根节点的位置
    for(int i=istart;i<iend;i++)
    {
        if(root_val==inorder[i])
        {
            iroot_index=i;
            break;
        }
    }
    int lefnum=iroot_index-istart;//左子树的长度,等于中序遍历中,根节点的位置减去开头

    root.left=buildTreeHelper(preorder,pstart+1,pstart+lefnum+1,inorder,istart,iroot_index);
    root.right=buildTreeHelper(preorder,pstart+lefnum+1,pend,inorder,iroot_index+1,iend);
    return root;
}

树的子结构

这题目很好,思路是什么

1.b是子结构,那么b的左右子树也就是a的左右子树的子结构

2.所以怎么办,递归的从a中寻找b的根节点看能不能找到,能找到那么则比较其左右子树是不是子结构即可

public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        //思路:递归,root2是子哦结构,那么root2的左右子树也都是子结构
        //所以我们先找roo1和root2相等的,然后找root1的左右子树有没有
        //返回条件是什么?有空
        return (root1!=null&&root2!=null)&&(recur(root1,root2)||HasSubtree(root1.left,root2)||HasSubtree(root1.right,root2));
        
    }


    //详解:因为要判断b是不是a的,所以b肯定比a小
    //所以这里比较子结构,其实就是一个一个去比较值等不等,等的话就再去递归判断他的左右子树上节点等不等
    //这里是比较两个部分是不是对应的子结构
    public boolean recur(TreeNode a,TreeNode b)
    {
        if(b==null)return true;//说明b已经比较完了,下面没了,那么说明是真
        if(a==null||a.val!=b.val)return false;//如果a为空,也就是说a不够长了,或者值不相等,那么肯定不是
        return recur(a.left,b.left)&&recur(a.right,b.right);
    }
}

二叉树的镜像

这题就是普通递归交换左右子树,但是牛客这个有点傻逼,非要我return一个什么东西

我return牛魔

public TreeNode Mirror (TreeNode root) {
    TreeNode p=root;
     if(root == null) {
        return null;
    }
    if(root.left == null && root.right == null) {
        return root;
    }
    TreeNode temp = root.left;
    root.left = root.right;//这样把整个子树也都交换过去了
    root.right = temp;
    Mirror(root.left);
    Mirror(root.right);
return p;

}

从上往下打印二叉树

byd这牛客给这个例子我都没看明白

这不就是层序遍历么?

public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
     ArrayList<Integer>res=new ArrayList<>();
     if(root==null)
        return res;
    Queue<TreeNode>q=new ArrayDeque<>();
    q.offer(root);
    while(!q.isEmpty())
    {
        TreeNode cur=q.poll();
        res.add(cur.val);
        if(cur.left!=null)
            q.offer(cur.left);
        if(cur.right!=null)
            q.offer(cur.right);
    }
    return res;
}

二叉搜索树的后序遍历序列

这题很容易想到分治法,但我还是不熟悉用helper

and终止条件没把握,我是傻逼

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence.length==0)return false;
        return recur(sequence,0,sequence.length-1);
    }
    public boolean recur(int []sequence,int i,int j)
    {
        if(i>=j)return true;//i>=j说明此子树结点数<=1,不用判断了
        int p=i;
        while(sequence[p]<sequence[j])p++;//sequence[len-1]为root
        int m=p;//m为第一个大于root值的位置,讲sequece分割为左右子树了[i,m-1]
        while(sequence[p]>sequence[j])p++;
        //此时p为第一个小于root值的位置,分割为[m,p]
        //按理来说,p应该等于j,因为小于sequence[j]的都应该在左子树当中
        //如果不等于,说明错了
        return (p==j)&&recur(sequence,i,m-1)&&recur(sequence,m,j-1);
    }
}

二叉树中和为某一值的路径(一)

我卡在哪里了:在想如果超过了怎么把前面的吐出来

解决办法:不用吐出来,没遍历到一个,tmpsum加p.val,然后比较即可

wait,此题定义路径为根节点到叶子结点,那么更不不需要讨论吐出来。。。我是傻逼

用加的,不用减的,超过了和没超过一样的,反正都是不等于

public class Solution {
   
    private static int sumVal=-1;
    private boolean flag=false;
    public boolean hasPathSum (TreeNode root, int sum) {
        sumVal=sum;
        // write code here
        dfs(root,0);
        return flag;
    }

    public void dfs(TreeNode p, int tmpsum)
    {
        if(p==null)return;
        if(p.left!=null)
        {
            dfs(p.left,tmpsum+p.val);
        }
        if(p.left==null&&p.right==null)
        {
            boolean tmpflag=tmpsum+p.val==sumVal;
            flag=flag||tmpflag;//防止别的路径已经找到了,但是被新的false覆盖
        }
        if(p.right!=null)
        {
            dfs(p.right,tmpsum+p.val);
        }
    }
}

二叉树中和为某一值的路径(二)

这题又让我练了一下回溯法

import java.util.*;


public class Solution {
    //回溯法+二叉树题目
    //回溯法不知道几部了,反正就是定义回溯函数,然侯加入,然后回调
    public ArrayList<ArrayList<Integer>> FindPath (TreeNode root, int target) {
       ArrayList<ArrayList<Integer>> res=new ArrayList<>();
        ArrayList<Integer> path=new ArrayList<>();
        if(root==null)return res;
        preorderdfs(root,target,res,path);//定义函数
        return res;
    }
    public void preorderdfs(TreeNode root,int targetsum,ArrayList<ArrayList<Integer>> res,ArrayList<Integer> path)
    {
        path.add(root.val);//加入
        if(root.left==null&&root.right==null)
        {
            if(targetsum-root.val==0)
            {
                res.add(new ArrayList<>(path));//这里一定要这么写,为什么我不知道
            }
            return;/*
            		为什么这里不remove呢,因为我们remove的操作实在回溯了之后的那个语句做的,发现没有
            		终止条件:找到叶子结点了,但是和不是target,终止
            		*/
        }
        if(root.left!=null)
        {
            preorderdfs(root.left,targetsum-root.val,res,path);
            path.remove(path.size()-1);//回溯
        }
        if(root.right!=null)
        {
            preorderdfs(root.right,targetsum-root.val,res,path);
            path.remove(path.size()-1);//回溯
        }
    }
}

二叉搜索树与双向链表

我妄想用一个指正解决问题,那不可能

public TreeNode head = null;  
//中序遍历当前值的上一位,初值为最小值,先定为null
public TreeNode pre = null;   
public TreeNode Convert(TreeNode pRootOfTree) {
    if(pRootOfTree == null)
        //中序递归,叶子为空则返回
        return null;     
    //首先递归到最左最小值  
    Convert(pRootOfTree.left); 
    //找到最小值,初始化head与pre
    if(pre == null){       
        head = pRootOfTree;
        pre = pRootOfTree;
    }
    //当前节点与上一节点建立连接,将pre设置为当前值
    else{       
        pre.right = pRootOfTree;
        pRootOfTree.left = pre;
        pre = pRootOfTree;
    }
    Convert(pRootOfTree.right);
    return head;
}

判断是不是平衡二叉树_

此题很简单吗,先递归求高度,然后递归求是不是平衡

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pRoot TreeNode类 
     * @return bool布尔型
     */
    public boolean IsBalanced_Solution (TreeNode pRoot) {
        // write code here
        if(pRoot==null)
            return true;
        int leftdeep=deep(pRoot.left);
        int rightdeep=deep(pRoot.right);
        if(leftdeep-rightdeep>1||leftdeep-rightdeep<-1)
            return false;
        return IsBalanced_Solution(pRoot.left)&&IsBalanced_Solution(pRoot.right);
    }
    public int deep(TreeNode root)
    {
        if(root==null)
            return 0;
        return Math.max(deep(root.left),deep(root.right))+1;
    }
}

二叉树的下一个结点

分情况讨论:

  1. 如果给出的结点有右子节点,则最终要返回的下一个结点即右子树的最左下的结点
  2. 如果给出的结点无右子节点,且当前结点是其父节点的左子节点,则返回其父节点
  3. 如果给出的结点无右子节点,且当前结点是其父节点的右子节点,则先要沿着左上方父节点爬树,一直爬到当前结点是其父节点的左子节点为止,返回的就是这个父节点;或者没有满足上述情况的则返回为NULL
public TreeLinkNode GetNext(TreeLinkNode node) {
    if(node.right!=null)
    {
        TreeLinkNode p=node.right;
        while(p.left!=null)
        {
            p=p.left;
        }
        return p;
    }
    if(node.next!=null&&node.next.left==node)
    {
        return node.next;
    }
    if(node.next!=null)
    {
        TreeLinkNode p=node.next;
        while(p.next!=null&&p.next.right==p)p=p.next;
        return p.next;
    }
    return null;
}

对称的二叉树

我的一个误区:最小元比较时还在判断left和right,然而只要比较值是不是相等就行了

public class Solution {

    public boolean isSymmetrical (TreeNode pRoot) {
        if(pRoot==null)return true;
        // write code here
        return Helper(pRoot.left,pRoot.right);
    }
    public boolean Helper(TreeNode r1,TreeNode r2)
    {
        if(r1==null&&r2==null)return true;
        if(r1==null||r2==null||r1.val!=r2.val)//就是这里,直接比较值就行了,不要比较left.val什么的
        {
            return false;
        }
        return Helper(r1.left,r2.right)&&Helper(r1.right,r2.left);
    }
}

把二叉树打印成多行

和“之字型打印“是一个道理

只要记住:判断是否已经一行,int n=dq.size()即可

public ArrayList<ArrayList<Integer>> Print (TreeNode pRoot) {
    // write code here
    Deque<TreeNode> dq=new LinkedList<TreeNode>();
    ArrayList<ArrayList<Integer>>res=new ArrayList<>();
    if(pRoot==null)return res;
    dq.offer(pRoot);
    while(!dq.isEmpty())
    {
        ArrayList<Integer>ans=new ArrayList<>();
        int n=dq.size();
        for(int i=0;i<n;i++)
        {
            TreeNode p=dq.pollFirst();
            ans.add(p.val);
            if(p.left!=null)
                dq.offer(p.left);
            if(p.right!=null)
                dq.offer(p.right);                    
        }
        res.add(new ArrayList<>(ans));
    }
    return res;
}

在二叉树中找到两个节点的最近公共祖先

此题很重要,建议每次做都看一下力扣的解答

用两个栈实现队列

没什么高级的,就是pop时把一个栈全pop到另一个栈里,在pop。。。

public:
    void push(int node) {
        stack1.push(node);
    }

    int pop() {
        while(!stack1.empty())
        {
            stack2.push(stack1.top());
            stack1.pop();
        }

        int res=stack2.top();
        stack2.pop();
        while(!stack2.empty())
        {
            stack1.push(stack2.top());
            stack2.pop();
        }
        return  res;
    }

private:
    stack<int> stack1;
    stack<int> stack2;

栈的压入、弹出序列

这一题解法的原理是什么?

如果压入和弹出序列是符合的,那么按照这个序列走,一定能走完

所以你只要按照给的顺序走一遍就行了

public boolean IsPopOrder (int[] pushV, int[] popV) {
    // write code here
    int n=pushV.length;
    Stack<Integer>s=new Stack<>();
    int j=0;
    for(int i=0;i<n;i++)
    {
        while(j<n&&(s.isEmpty()||s.peek()!=popV[i]))
        {
            s.push(pushV[j]);
            j++;
        }
        if(s.peek()==popV[i])
        {
            s.pop();
        }else
        return false;
    }
    return true;
}

翻转单词序列

熟悉javaapi用的

public String ReverseSentence(String str) {
    String []arr=str.split(" ");
    Stack<String>stk=new Stack<String>();
    for(String s:arr)
    {
        stk.push(s);
    }
    StringBuilder res=new StringBuilder();
    while(!stk.isEmpty()){
        res.append(stk.pop());
        res.append(" ");
    }
    return res.toString().substring(0,res.toString().length()-1);
}

滑动窗口的最大值

用优先队列,注意如何创建最大堆

public ArrayList<Integer> maxInWindows (int[] num, int size) {
    // write code here
    ArrayList<Integer> res=new ArrayList<>();
    if(num.length==0||size==0||num.length<size)return res;
    PriorityQueue<Integer>pq=new PriorityQueue<>((o1,o2)->o2-o1);
    for(int i=0;i<size;i++)pq.add(num[i]);
    res.add(pq.peek());
    for(int i=1;i+size-1<num.length;i++)
    {
        pq.remove(num[i-1]);
        pq.add(num[i+size-1]);
        res.add(pq.peek());
    }
    return res;

}

搜索算法部分

数字在升序数组中出现的次数

private int bisearch(int[] data, double k){ 
    int left = 0;
    int right = data.length - 1;
    //二分左右界
    while(left <= right){ 
        int mid = (left + right) / 2;
        if(data[mid] < k)
            left = mid + 1;
        else if(data[mid] > k)
            right = mid - 1;
    }
    return left;
}
public int GetNumberOfK(int [] array , int k) {
    //分别查找k+0.5和k-0.5应该出现的位置,中间的部分就全是k
    return bisearch(array, k + 0.5) - bisearch(array, k - 0.5);
}

一眼二分,但是我一开始想说递归二分,然后发现只要求左右边界就行了

思路二:直接求左右边界

import java.util.*;


public class Solution {
    
     //思路:二分找左边界,二分找有边界,然后相减即可
    public int GetNumberOfK (int[] nums, int k) {
        int a=getLeft(nums,k);
        int b=getRight(nums,k);
        return b-a-1;
    }

    public int getLeft(int []nums,int k)
    {
        int l=0,r=nums.length-1;//左臂右臂
        while(l<=r)
        {
            int mid=(l+r)/2;
            if(nums[mid]<k)
            {
                l=mid+1;
            }
            else
            {
                r=mid-1;
            }
        }
        return r;
    }

    public int getRight(int []nums, int k)
    {
        int l=0,r=nums.length-1;
        while(l<=r)
        {
            int mid=(l+r)/2;
            if(nums[mid]>k)
            {
                r=mid-1;
            }else
            {
                l=mid+1;
            }
        }
        return l;
    }
}

旋转数组的最小数字

这题和力扣上还不同,这题剑指有相同值,力扣没有

所以这一题,剑指offer的方法更有通用性,掌握这个

public int minNumberInRotateArray (int[] nums) {
    //思路:旋转后的数组分为两个部分,也就是两个升序序列
    //我们要找的值其实就是第二段升序序列的第一个数
    //我们找到mid,然后和nums[j]比较,如果小于他,那么nums[mid]就在第二段序列里
    //如果大于他,那么就在第一段序列里,那么更新left
    //如果小于他那么就在第二段 序列里面,更新right
    int l=0,r=nums.length-1;
    while(l<r)//左闭右开
    {
        int m=(l+r)/2;

        if(nums[m]>nums[r])
        {
            l=m+1;//左边闭区间,所以必须+1
        }else if(nums[m]<nums[r])
        {
            r=m;//右边开区间,所以不用
        }else
        {
            r--;
        }
    }
   return nums[l]; 
}

注意事项在注释里面

二维数组中的查找

public boolean Find (int target, int[][] array) {
    // write code here
   int i=0,j=array[0].length-1;
   while(i<array.length&&j>=0)
   {
    if(target>array[i][j])
    {
        i++;
    }
    else if(target<array[i][j])
    {
        j--;
    }
    else
    {
        return true;
    }
   }
   return false;
}

思路啊,三种方法:

  • 两层遍历
  • 对每个子数组用二分
  • 这种方法,鉴定为观察能力极强

字符串的排列_牛客题霸_牛客网 (nowcoder.com)

这个题目要注意的是:字符串里有重复字符,abb’ab’bbab’b’abbb’ab’ba都是重复的,难点在于去除这些重复

由此为了去重,我们可以定义一个规则,即对于相同数,我们人为定序,如上例子中11'2345,我们指定重复数字首次要放入某一个框位时,只能放重复数字的第一个,如这里只能放1,放1'的情况直接过滤,其他框位同理

具体做法:

  • step 1:先对字符串按照字典序排序,获取第一个排列情况。

  • step 2:准备一个空串暂存递归过程中组装的排列情况。使用额外的vis数组用于记录哪些位置的字符被加入了。

  • step 3:每次递归从头遍历字符串,获取字符加入:首先根据vis数组,已经加入的元素不能再次加入了;同时,如果当前的元素str[i]与同一层的前一个元素str[i-1]相同且str[i-1]已经用,也不需要将其纳入。

  • step 4:进入下一层递归前将vis数组当前位置标记为使用过。

  • step 5:回溯的时候需要修改vis数组当前位置标记,同时去掉刚刚加入字符串的元素,

  • step 6:临时字符串长度到达原串长度就是一种排列情况。

    import java.util.*;
    
    
    public class Solution {
        
        public ArrayList<String> Permutation (String str) {
            // write code here
            ArrayList<String>res=new ArrayList<>();
            char[]charStr=str.toCharArray();
            Arrays.sort(charStr);
            boolean[]visit=new boolean[charStr.length];
            StringBuffer temp=new StringBuffer();
            back(res,charStr,temp,visit);
            return res;
    
        }
        public void back(ArrayList<String>res,char[]str,StringBuffer temp,boolean[]visit)
        {
            if(temp.length()==str.length)
            {
                res.add(new String(temp));
                return;
            }
            for(int i=0;i<str.length;i++)
            {
                if(visit[i])
                {
                    continue;//这个元素用过了,跳过
                }
                if(i>0&&str[i-1]==str[i]&&!visit[i-1])//这里表示的是回溯过来的,1 1' 2 3 从1’开始取的情况,这个时候因为回溯了,所以1的visit的false,但是1' 与1会重复,所以不能取
                {
                    continue;
                }
                visit[i]=true;
                temp.append(str[i]);
                back(res,str,temp,visit);
                visit[i]=false;
                temp.deleteCharAt(temp.length()-1);
            }
        }
    }

LCR 163. 找到第 k 位数字 - 力扣(LeetCode)

这题有一说一,规律太难找,只能反复刷记住了

public int findKthNumber(int k) {
  //k是整个序列中的位置
  int digit=1;//位数,一位数二位数这种
  long start=1;//每位数的起始数字,就是1,10,100,1000
  long count=9;//每种位数的数,各占多少位置,个位数9位,十位数180位

  //首先k所在的数字是几位数,一直减掉1、2、3.。。。位数的数量即可
  while(k>count)
  {
    k-=count;
    start*=10;
    digit+=1;
    count=9*start*digit;
  }

  //减完之后,最后剩下的k其实就是,第k个digit位数(从 0开始)
  //然后获得,k所在的是哪一个数字
  long num=start+(k-1)/digit;//因为序列从第“0”个数字开始,start是n位数的第一个,也就是10,100,1000.。。然后k-1/digit相当于偏移量
  //确定他是哪一位
 String s=Long.toString(num);
 int res=s.charAt((k-1)%digit)-'0';

return res;
}

动态规划

连续子数组的最大和

记住杀卡尔的动态规划五部曲

public int FindGreatestSumOfSubArray (int[] array) {
    // write code here
    int []dp=new int[array.length];
    dp[0]=array[0];
    for(int i=1;i<array.length;i++)
    {
        dp[i]=Math.max(dp[i-1]+array[i],array[i]);
    }
    int res=Integer.MIN_VALUE;
    for(int i=0;i<dp.length;i++)
    {
        res=Math.max(res,dp[i]);
    }
    return res;
}

连续子数组的最大和(二)

思路其实就是维护left和right,因为要返回最长的,所以还得维护maxleft,maxright

public int[] FindGreatestSumOfSubArray (int[] array) {
    // write code here
    int len = array.length;
    int []dp = new int[len];
    ArrayList<Integer>list = new ArrayList<>();
    dp[0] = array[0];
    int left = 0, right = 0;
    int maxleft = 0, maxright = 0;
    int maxsum = dp[0];//注意此处初始化
    for (int i = 1; i < len; i++) {
        right++;
        dp[i] = Math.max(dp[i - 1] + array[i], array[i]);
        if (dp[i - 1] + array[i] < array[i]) {
            left = right; //重置了
        }
        //遇到新的最大值了
        if (dp[i] > maxsum || dp[i] == maxsum &&
                (right - left + 1) > (maxright - maxleft + 1)) { //因为要返回最长的
            maxsum = dp[i];
            maxleft = left;
            maxright = right;
        }
    }
    int []res = new int[maxright - maxleft + 1];
    for(int i=maxleft,j=0;i<=maxright;i++,j++)
    {
        res[j]=array[i];
    }
    return res;
    }

斐波那契数列

public int Fibonacci (int n) {
    // write code here
    int []dp=new int [n+1];
    dp[1]=1;
    dp[2]=1;
    for(int i=3;i<=n;i++)
    {
        dp[i]=dp[i-1]+dp[i-2];
    }
    return dp[n];
}

礼物的最大价值

比较简单,和机器人走路是一个套路

public int maxValue (int[][] grid) {
    // write code here
    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<n;i++)
    {
        dp[0][i]=dp[0][i-1]+grid[0][i];
    }
    for(int j=1;j<m;j++)
    {
        dp[j][0]=dp[j-1][0]+grid[j][0];
    }
    for(int i=1;i<m;i++)
    {
        for(int j=1;j<n;j++)
        {
            dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1])+grid[i][j];
        }
    }
    return dp[m-1][n-1];
}

最长不含重复字符的子字符串

说是动态规划,但我一开始用的暴力

public int lengthOfLongestSubstring (String s) {
    // write code here
    int left=0,right=1;
    if(s.length()==0)return 0;
    if(s.length()==1)return 1;
    int maxlen=0;
    while(right<s.length())
    {
        if(hassame(s.substring(left,right+1)))
        {
            left++;
        }else
        {
            maxlen=Math.max(maxlen,right-left+1);
            right++;
        }
    }
    return maxlen;
}
public boolean hassame(String s)
{
    Set<Character>set=new HashSet<Character>();
    for(int i=0;i<s.length();i++)
    {
        if(!set.add(s.charAt(i)))
        {
            return true;
        }
    }
    return false;
}

后来我自己写的动态规划

public int lengthOfLongestSubstring (String s) {
    // write code here
    //思路:好像和最大子数组和差不多?
    //dp[i]:以i结尾的最长不包含重复字符的子串长度
    //dp[i]=dp[i-1]+1,当s.charat(i)不和前面的重复
    //否则dp[i]=1
    //如何判断充不重复呢
    int len=s.length();
    int[]dp=new int[len];
    dp[0]=1;
    int left=0,right=0;
    int maxleft=0,maxright=0;
    int maxlen=1;
    for(int i=1;i<len;i++)
    {
        right++;
        char c=s.charAt(i);
        if(s.substring(left,right).indexOf(c)>=0)
        {
            dp[i]=1;
            left=right;
        }else
        {
            dp[i]=dp[i-1]+1;
            if(right-left+1>maxlen)
            {
                maxlen=right-left+1;
                maxleft=left;
                maxright=right;
            }
        }
    }
    return maxlen;
}

又错了,发现错哪了吗

更新left的时候,我们应该更新到重复元素的第一个个左边,而不是直接更新到right

修改:

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param s string字符串 
     * @return int整型
     */
    public int lengthOfLongestSubstring (String s) {
        // write code here
        //思路:好像和最大子数组和差不多?
        //dp[i]:以i结尾的最长不包含重复字符的子串长度
        //dp[i]=dp[i-1]+1,当s.charat(i)不和前面的重复
        //否则dp[i]=1
        //如何判断充不重复呢
        int len=s.length();
        int[]dp=new int[len];
        dp[0]=1;
        int left=0,right=0;
        int maxleft=0,maxright=0;
        int maxlen=1;
        for(int i=1;i<len;i++)
        {
            right++;
            char c=s.charAt(i);
            int index=s.indexOf(c,left);
            if(index>=left&&index<right)//修改部分
            {
                dp[i]=1;
                left=index+1;
            }else
            {
                dp[i]=dp[i-1]+1;
                if(right-left+1>maxlen)
                {
                    maxlen=right-left+1;
                    maxleft=left;
                    maxright=right;
                }
            }
        }
        return maxlen;
    }
}

力扣上给了我们滑动窗口的方法

public int lengthOfLongestSubstring(String s) {
        HashMap<Character, Integer> map = new HashMap<>();
        int maxLen = 0;//用于记录最大不重复子串的长度
        int left = 0;//滑动窗口左指针
        for (int i = 0; i < s.length() ; i++)
        {
            /**
            1、首先,判断当前字符是否包含在map中,如果不包含,将该字符添加到map(字符,字符在数组下标),
             此时没有出现重复的字符,左指针不需要变化。此时不重复子串的长度为:i-left+1,与原来的maxLen比较,取最大值;

            2、如果当前字符 ch 包含在 map中,此时有2类情况:
             1)当前字符包含在当前有效的子段中,如:abca,当我们遍历到第二个a,当前有效最长子段是 abc,我们又遍历到a,
             那么此时更新 left 为 map.get(a)+1=1,当前有效子段更新为 bca;
             2)当前字符不包含在当前最长有效子段中,如:abba,我们先添加a,b进map,此时left=0,我们再添加b,发现map中包含b,
             而且b包含在最长有效子段中,就是1)的情况,我们更新 left=map.get(b)+1=2,此时子段更新为 b,而且map中仍然包含a,map.get(a)=0;
             随后,我们遍历到a,发现a包含在map中,且map.get(a)=0,如果我们像1)一样处理,就会发现 left=map.get(a)+1=1,实际上,left此时
             应该不变,left始终为2,子段变成 ba才对。

             为了处理以上2类情况,我们每次更新left,left=Math.max(left , map.get(ch)+1).
             另外,更新left后,不管原来的 s.charAt(i) 是否在最长子段中,我们都要将 s.charAt(i) 的位置更新为当前的i,
             因此此时新的 s.charAt(i) 已经进入到 当前最长的子段中!
             */
            if(map.containsKey(s.charAt(i)))
            {
                left = Math.max(left , map.get(s.charAt(i))+1);
            }
            //不管是否更新left,都要更新 s.charAt(i) 的位置!
            map.put(s.charAt(i) , i);
            maxLen = Math.max(maxLen , i-left+1);
        }
        
        return maxLen;
    }

其实这个方法就是用i,代替了right

把数字翻译成字符串

public int solve (String nums) {
    if(nums.length()==0||nums.charAt(0)=='0')
        return 0;
    int []dp=new int[nums.length()];
    dp[0]=1;
    for(int i=1;i<dp.length;i++)
    {
        if(nums.charAt(i)!='0')
        {
            dp[i]=dp[i-1];//先默认都一样
        }
        int num=(nums.charAt(i-1)-'0')*10+(nums.charAt(i)-'0');//计算自己和前一个平起来的两位数
        if(num>=10&&num<=26)
        {
            //如果在范围内可以表示
            if(i==1)
                dp[i]+=1;
            else
                dp[i]+=dp[i-2];
        }
    } 
    return dp[nums.length()-1];   
}

有条件的青蛙跳台阶问题,需要判断i-1和i组成的二位数是否可以表示,这里的关键在于如何处理“0

10. 正则表达式匹配 - 力扣(LeetCode)

这题目太他妈难了,面试之前突击一下吧

public boolean isMatch(String s, String p) {
    //dp[i][j],s0 i-1,是否匹配p 0 ,j-1这样方便初始化
    //这题也太他妈难了
    if(s==null||p==null)return false;

    int slen=s.length(),plen=p.length();
    boolean[][]dp=new boolean[slen+1][plen+1];
    dp[0][0]=true;
    for(int j=1;j<plen+1;j++)
    {
        if(p.charAt(j-1)=='*')dp[0][j]=dp[0][j-2];//s是空串,如果j-1的位置是*,那么就把j-2删掉,再比较前面的
    }

    for(int i=1;i<slen+1;i++)
    {
        for(int j=1;j<plen+1;j++)
        {
            if(s.charAt(i-1)==p.charAt(j-1)||p.charAt(j-1)=='.')//如果相等或者j是点,因为点可以任意匹配,
            {
                dp[i][j]=dp[i-1][j-1];
            }else if(p.charAt(j-1)=='*')//如果是*,那么得看i-1和j-2匹不匹配
            {
                if(s.charAt(i-1)==p.charAt(j-2)||p.charAt(j-2)=='.')
                {
                    dp[i][j]=dp[i][j-2]||dp[i-1][j-2]||dp[i-1][j];
                }else{
                dp[i][j]=dp[i][j-2];
            }
            }
        }
    }
    return dp[slen][plen];
}

排序算法

数组中重复的数字

public int duplicate (int[] numbers) {
    // write code here
    HashSet<Integer>set=new HashSet<Integer>();
    for(int i=0;i<numbers.length;i++)
    {
        if(!set.add(numbers[i]))
        {
            return numbers[i];
        }
    }
    return -1;
}

用set,鉴定为易

最小的K个数

public ArrayList<Integer> GetLeastNumbers_Solution (int[] input, int k) {
    // write code here
    ArrayList<Integer>list=new ArrayList<Integer>();
    Arrays.sort(input);
    for(int i=0;i<k;i++)
    {
        list.add(input[i]);
    }
    return list;
}

· 以上 为弱智解法

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param input int整型一维数组 
     * @param k int整型 
     * @return int整型ArrayList
     */
   public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
    ArrayList<Integer> res = new ArrayList<>(k);
    //根据题意要求,如果K>数组的长度,返回一个空的数组
    if (k > input.length || k == 0)
        return res;
    quickSort(input, res, k, 0, input.length - 1);
    return res;
}
 
private void quickSort(int[] input, ArrayList<Integer> res, int k, int left, int right) {
    //快排的实现方式有多种,我们选择了其中的一种
    int start = left;
    int end = right;
    while (left < right) {
        while (left < right && input[right] >= input[start]) {
            right--;
        }
        while (left < right && input[left] <= input[start]) {
            left++;
        }
        swap(input, left, right);
    }
    swap(input, left, start);
    //注意这里,start是数组中元素的下标。在start之前的元素都是比start指向的元素小,
    //后面的都是比他大。如果k==start,正好start之前的k个元素是我们要找的,也就是
    //数组中最小的k个,如果k>start,说明前k个元素不够,我们还要往后再找找。如果
    //k<start,说明前k个足够了,我们只需要在start之前找k个即可。
    if (left > k) {
        quickSort(input, res, k, start, left - 1);
    } else if (left < k) {
        quickSort(input, res, k, left + 1, end);
    } else {
        //取前面的k个即可
        for (int m = 0; m < k; ++m) {
            res.add(input[m]);
        }
    }
}
 
private void swap(int[] arr, int i, int j) {
    if (i == j)
        return;
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
}

· 快排,一次排序可以确认pivot在排序后的数组里的位置,只要比较这个位置和k即可

然而还有更好的方法,那就是堆排序

public ArrayList<Integer> GetLeastNumbers_Solution (int[] input, int k) {
    // write code here
    PriorityQueue<Integer>pq=new PriorityQueue<>();//默认创建是小顶堆
    for(int i=0;i<input.length;i++)
    {
        pq.add(input[i]);
    }
    ArrayList<Integer>res=new ArrayList<>();
    while(k>0)
    {
        res.add(pq.peek());
        pq.poll();
        k--;
    }
    return res;
}

数组中的逆序对

弱智方法一:

冒泡排序,记录总共交换了多少次

经典最后一个用例超时

public int InversePairs (int[] nums) {
    // write code here
    int res=0;
    for(int i=0;i<nums.length-1;i++)
    {
        for(int j=0;j<nums.length-i-1;j++)
        {
            if(nums[j]>nums[j+1])
            {
                swap(nums,j,j+1);
                res++;
            }
        }
    }
    return res;
}
public void swap(int nums[],int i,int j)
{
    int t=nums[i];
    nums[i]=nums[j];
    nums[j]=t;
}

归并排序法:归并排序爷还不熟,后面继续熟悉

class Solution {
    int count=0;
    public int reversePairs(int[] nums) {
        this.count=0;
        mergeSort(nums,0,nums.length-1);
        return count;
    }
    public void mergeSort(int []nums,int left,int right)
    {
        if(left>=right)return ;
        int mid=(left+right)/2;

        mergeSort(nums,left,mid);
        mergeSort(nums,mid+1,right);
        merge(nums,left,mid,right);
    }

    public void merge(int []nums,int left,int mid,int right)
    {
        int []tmp=new int[right-left+1];
        int i=left;
        int j=mid+1;
        int t=0;
        while(i<=mid&&j<=right)
        {
            if(nums[i]<=nums[j])
            {
                tmp[t++]=nums[i++];
            }else
            {
                count+=mid-i+1;
                tmp[t++]=nums[j++];
            }
        }
        while(i<=mid)
            tmp[t++]=nums[i++];
        while(j<=right)
            tmp[t++]=nums[j++];

        for(int k=0;k<tmp.length;k++)
            nums[left+k]=tmp[k];
    }
}

数据流中的中位数_牛客题霸_牛客网 (nowcoder.com)

这个思路很无敌,一个最小堆一个最大堆,那么两个的头就是中间的元素

public class Solution {

    PriorityQueue<Integer> max=new PriorityQueue<>();//保存较大的一般
    PriorityQueue<Integer> min=new PriorityQueue<>((o1,o2)->o2.compareTo(o1));
//保存较小的一半
    public void Insert(Integer num) {
        min.offer(num);//先进较小的一半
        max.offer(min.poll());//然后把较小的里面最大的弄到较大的一半里面
        //因为java里/2是四舍五入的,所以要保证较小的一半比较大的一半不少
        if(min.size()<max.size())
            min.offer(max.poll());
    }

    public Double GetMedian() {
        if(min.size()>max.size())//大,说明是奇数个
        {
            return(double) min.peek();
        }
        else//相等,说明是偶数个
        {
            return (double)(min.peek()+max.peek())/2;
        }
    }

位运算部分

傻逼位运算是最恶心的题目,我操你妈

数值的整数次方

思路:快速幂方法

计算x64,可以直接,x,x2,x4,x8….

计算x77,可以直接x,x2,x4…..x76,最后再乘x得到x77

但是你从左往右你怎么知道要不要乘x呢

所以我们倒过来求,对于x(a),我们求x(a/2) =yjava中是向下取整

要是a为偶数,xa=y2,要是是奇数,xa=y2*x

即可

//为了防止坑爹用力,把n变成long
public class Solution {
    public double Power(double x, int n) {
        long N =n;
        return N>=0?quick(x,N):1.0/quick(x,-N);
  }
  public double quick(double x, long n)
  {
        if(n==0)return 1.0;
        double y=quick(x,n/2);
        return n%2==0?y*y:y*y*x;
  }
}

求1+2+3+…+n

方法一:没说不能用递归啊,那我们可以直接递归

public int Sum_Solution(int n) {
    return n==0?0:Sum_Solution(n-1)+n;
}

二进制中1的个数

记住:有一个性质:n&(n-1)可以消除二进制表示的n中最后的一个1,反正你记住就行了

public int NumberOf1 (int n) {
    // write code here
    int res=0;
    while(n!=0)
    {
        res+=1;
        n=n&(n-1);//不断这样,直到n里1全没了,那就是n变成0了
    }
    return res;
}

数组中只出现一次的两个数字

首先题目对空间复杂度有要求,所以不能用哈希表

我们知道,数组中只出现一次的一个数字,可以直接将数组里所有的数异或(^) 利用异或同0异1的性质求出来

但是此处是两个数字,怎么办呢

知识点:x&-x可以取出二进制x中最低位的那个1

那么我们要把数字分为两类

如果我们任然将所有的数字都异或,得到一个x,那么他就是只出现一次的两个数字x1与x2的异或

然后我们将x&-x,得到它最低位的那个1,我们假设这一位是k位

因为是二进制,所以所有数的k位只有两种可能,1或者0

这样把所有的数分成了两类,一类是k位是0,一类是k位是1

因为除了x1,x2 其他都出现了两次

所以出现了两次的数,会被分在同一类中,而因为x1异或x2,也就是x第k位是1,所以x1和X2的第k位必然不同

所以x1与x2会被分为不同的类当中

public int[] FindNumsAppearOnce (int[] nums) {
    // write code here
    int xorsum=0;
    for(int num:nums)
    {
        xorsum^=num;
    }
    int lsb=(xorsum==Integer.MIN_VALUE?xorsum:xorsum&(-xorsum));//xorsum是-2147483648的时候变为正数会溢出,因为32位的int正数范围是0~2147483647,而为什么是min.value时,直接返回xorsum呢,因为-2147483638二进制是1000000000,最低位的1其实就是她自己
    int t1=0,t2=0;
    for(int num:nums)
    {
        if((lsb&num)!=0)
        {
            t1^=num;
        }else
        {
            t2^=num;
        }
    }
    if(t1>t2)
    {
        return new int[]{t2,t1};
    }
    return new int[]{t1,t2};
}

其他算法部分

构建乘积数组

此题思路也无敌,但我没看懂,二刷的时候再看看吧

public int[] multiply (int[] A) {
    // write code here
    int[]B=new int[A.length];
    B[0]=1;
    for(int i=1;i<A.length;i++)
        B[i]=B[i-1]*A[i-1];
    int temp=1;
    for(int i=A.length-1;i>=0;i--)
    {
        B[i]*=temp;
        temp*=A[i];
    }
    return B;
}

第一个只出现一次的字符

考察方法的

public int FirstNotRepeatingChar (String str) {
    // write code here
    for(int i=0;i<str.length();i++)
    {
        if(str.lastIndexOf(str.charAt(i))==i&&str.indexOf(str.charAt(i))==i)
        {
            return i;
        }
    }
    return -1;
}

调整数组顺序使奇数位于偶数前面(一)

这题目南软有一种快速排序的方法,但我我忘了

方法一:就是建新数组

public int[] reOrderArray (int[] array) {
    // write code here
    int n=array.length;
    int []res=new int[n];
    int odd=0;
    for(int i=0;i<n;i++)
    {
        if(array[i]%2==1)
            odd++;
    }
    int x=0,y=odd;
    for(int i=0;i<n;i++)
    {
        if(array[i]%2==1)
        {
            res[x]=array[i];
            x++;
        }
        else
        {
            res[y]=array[i];
            y++;
        }
    }
    return res;
}

方法二:原地排序,先鸽了

//鸽了

把数组排成最小的数

思路:对于string类型,a+b>b+a的话,那么就排序

public String PrintMinNumber (int[] numbers) {
    // write code here
    if(numbers==null||numbers.length==0)
        return "";
    String[]nums=new String[numbers.length];
    for(int i=0;i<numbers.length;i++)
        nums[i]=numbers[i]+"";
    Arrays.sort(nums,new Comparator<String>(){//重写compare函数
        public int compare(String s1,String s2){
            return (s1+s2).compareTo((s2+s1));//规则是:返回值大于0则交换
            //String.compareto:如果左大于右,则返回大于0的值
            //跟这里需求是一样的,把大的换到后面
        }
    });
    StringBuilder res=new StringBuilder();
    for(int i=0;i<nums.length;i++)
        res.append(nums[i]);
    return res.toString();
}

整数中1出现的次数(从1到n整数中1出现的次数)

我的评价:看k神的题解,背下来

public int countDigitOne(int n) {
    int digit=1,res=0;
    int high=n/10,cur=n%10,low=0;
    while(high!=0||cur!=0)
    {
        if(cur==0)res+=high*digit;
        else if(cur==1)res+=high*digit+low+1;
        else res+=(high+1)*digit;
        low+=cur*digit;
        cur=high%10;
        high/=10;
        digit*=10;
    }
    return res;
}

和为S的连续正数序列

滑动窗口法,简直是天才

public ArrayList<ArrayList<Integer>> FindContinuousSequence (int sum) {
    // write code here
    //滑动窗口法,大了就左边出去,小了就右边进来
   ArrayList<ArrayList<Integer>> res=new ArrayList<>();
   int low=1,high=2;
   while(low<high)
   {
        int cur=(high+low)*(high-low+1)/2;//计算和
        if(cur==sum)
        {
            ArrayList<Integer> list=new ArrayList<>();
            for(int i=low;i<=high;i++)
            {
                list.add(i);
            }
            res.add(list);
            low++;
        }
        else if(cur<sum)
        {
            high++;
        }
        else
        {
            low++;
        }
   }
   return res;
}

和为S的两个数字

我的误区:还是l=0,r=1去滑动窗口,但是这样不对

应该r=最末尾的地方,这样可以避免什么,避免你们从同一侧向右,然后r++,而l又要从0开始遍历,那不就相当于两层for了吗

public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
    ArrayList<Integer> res = new ArrayList<Integer>();
    //左右双指针
    int left = 0, right = array.length - 1;
    //对撞双指针
    while(left < right){
        //相加等于sum,找到目标
        if(array[left] + array[right] == sum){
            res.add(array[left]);
            res.add(array[right]);
            break;
        //和太大,缩小右边
        }else if(array[left] + array[right] > sum)
            right--;
        //和太小,扩大左边
        else
            left++;
    }
    return res;
}

打印从1到最大的n位数

public int[] printNumbers (int n) {
    // write code here
    int size=1;
    while(n>0)
    {
        size=size*10;
        n--;
    }
    int[]res=new int[size-1];
    for(int i=1;i<size;i++)
    {
        res[i-1]=i;
    }
    return res;
}

暴力,没啥可说的

丑数

思路:所以我们就将res[i]去乘以 2、3、5,然后比较出最小的那个,就是我们当前的下一个丑数了。

但是前面有那么多数,如何比较呢,维护i2,i3,i5

public int GetUglyNumber_Solution (int index) {
    // write code here
    if(index<=6)return index;
    int i2=0,i3=0,i5=0;//从1开始,每个丑数都需要乘一次2,3,5
    //i2,i3,i5代表的就是从1开始,每个丑数乘2,3,5
    int[] res=new int[index];
    res[0]=1;//1是第一个丑数

    for(int i=1;i<index;i++)
    {
        res[i]=Math.min(res[i2]*2,Math.min(res[i3]*3,res[i5]*5));
        if(res[i]==res[i2]*2)//如果这一次被选择的是i2上的数乘2,那么i2++
            i2++;
        if(res[i]==res[i3]*3)
            i3++;
        if(res[i]==res[i5]*5)
            i5++;
    }
    return res[index-1];
}

孩子们的游戏(圆圈中最后剩下的数)

想起来一个思路了,那就是倒过来推,推出剩下的那个在原来全员都在的情况下,他的下标是什么

这题还不能用模拟,因为会超时

我们看力扣题解里的”换个角度理解xxx”

制定规则:当去除一个人之后,把去除的那个人的后一个人作为下标0,然后去除的人的前面的人 全部排到后面去

只剩最后一个人的时候,他的下标必定是0,然后我们反推出他在人全在的时候下标是哪个

约瑟夫环1.png

正过来:先删除,然后左移m-1位

反过来:先加回去,然后右移m位,如果溢出了,就加到最前面

反过来的公式:[当前位置+m(右移m位)]%i(如果溢出就加到最前面) i是当前有多少个数字

public int iceBreakingGame(int num, int target) {
    int pos=0;//最后剩下下标肯定是0
    for(int i=2;i<=num;i++)
    {
        pos=(pos+target)%i;
    }
    return pos;
}

字符流中第一个不重复的字符_牛客题霸_牛客网 (nowcoder.com)

第一种弱智方法:

public class Solution {
    //Insert one char from stringstream
    HashMap<Character,Integer>map=new HashMap<>();
    StringBuilder s=new StringBuilder();
    public void Insert(char ch)
    {
        map.put(ch,map.getOrDefault(ch,0)+1);
        s.append(ch);
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        for(int i=0;i<s.length();i++)
        {
            if(map.get(s.charAt(i))==1)
            {
                return s.charAt(i);
            }
        }
        return '#';
    }
}

第二种:用linkedHashmap的有序性

public class Solution {
    //Insert one char from stringstream
    private Map<Character,Integer>map=new LinkedHashMap<>();
    public void Insert(char ch)
    {
        if(map.containsKey(ch))
        {
            map.put(ch,map.get(ch)+1);
        }else
        {
            map.put(ch,1);
        }
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        for(Map.Entry<Character,Integer>set:map.entrySet())
        {
            if(set.getValue()==1)
            {
                return set.getKey();
            }
        }
        return '#';
    }
}

比第一种快了一半

LCR 138. 有效数字 - 力扣(LeetCode)

y用宫水三叶的方法模拟

class Solution {
    //按照宫水三叶的思路,先进行处理,然后分情况判断,如果有E,那就分别判断左右,没有就整体判断
    //然后我们把所有的e换成E
    //小数:就是小数点的左右至少有一方有数字
    //整数:至少有一位数字
    //E的左边可以是整数和小数,右边必须是整数
    public boolean validNumber(String s) {
        s=s.trim();
        int n=s.length(),l=0,r=n-1;
        s=s.replace('e', 'E');
        int idx=0;
        while(idx<n&&s.charAt(idx)!='E')idx++;
        if(idx==n)
        {
            return check(s, true);
        }else
        {
            return check(s.substring(0,idx), true)&&check(s.substring(idx+1), false);
        }
    }

    public boolean check(String s ,boolean isDecimal)
    {
        if(s.equals(".")||s.equals(""))return false;//这一半只有个符号和空,那就false
        int n=s.length();
        boolean once=true;//是不是只有一个小数点
        for(int i=0;i<n;i++)
        {
            char c=s.charAt(i);
            if(c=='+'||c=='-')
            {
                if(i!=0||i==n-1)return false;
            }else if(c=='.')
            {
                if(!isDecimal)//不被允许有小数的部分
                {
                    return false;
                }
                if(!once)return false;//已经出现过小数点了
                once=false;
                boolean a=i-1>=0&&Character.isDigit(s.charAt(i-1));//左边是不是数字
                boolean b=i+1<n&&Character.isDigit(s.charAt(i+1));//右边是不是数字
                if(!(a||b))return false;
            }else if(!Character.isDigit(c)){
                return false;
            }
        }
        return true;
    }

}

LCR 131. 砍竹子 I - 力扣(LeetCode)

数学结论:尽量以长度3切,这样最大

public int cuttingBamboo(int n) {
    if(n<4)return n-1;
    int res=1;
    while (n>4) {
        res*=3;
        n-=3;
    }
    return res*n;
}

LCR 132. 砍竹子 II - 力扣(LeetCode)

还是和上一题一样的思路,只不过在循环过程和最后要取模

public int cuttingBamboo(int n) {
    if(n<4)return n-1;
    long res=1;
    while(n>4)
    {
        res=res*3%1000000007;
        n-=3;
    }
    return (int)(res*n%1000000007);
}\
    //积的结果取余等于 每个乘数取余的乘积再取余

但是在牛客里,这样的方法会超时,我们采用快速幂的方法,可以将时间复杂度降低

回溯

79. 单词搜索 - 力扣(LeetCode)

class Solution {
    public boolean exist(char[][] board, String word) {
        int h = board.length, w = board[0].length;
        boolean[][] visited = new boolean[h][w];
        for(int i=0;i<h;i++)
        {
            for(int j=0;j<w;j++)
            {
                boolean flag=check(board,visited,i,j,word,0);
                if(flag)
                {
                    return true;
                }
            }
        }
        return false;
    }
    public boolean check(char[][]board,boolean[][]visited,int i ,int j,String s,int k)
    {
        if(board[i][j]!=s.charAt(k))
        {
            return false;
        }else if(k==s.length()-1)//长度一样了
        {
            return true;
        }
        visited[i][j]=true;
        boolean result=false;
        int[][]dirs={{0,1},{0,-1},{1,0},{-1,0}};
        for(int[]dir:dirs)
        {
            int newi=i+dir[0],newj=j+dir[1];
            if(newi>=0&&newi<board.length&&newj>=0&&newj<board[0].length)
            {   
                if(!visited[newi][newj])
                {
                boolean flag=check(board,visited,newi,newj,s,k+1);//递归查询他周边的
                if(flag)
                {
                    result=true;//说明这条路径可以找到
                    break;//不用再找了
                }
                }
            }
        }
        visited[i][j]=false;//回溯
        return result;
    }
    
}

应该要知道flag是什么用处?是用来剪枝的,就是说找到了 一条路径后,直接设置flag为true,退出整个搜索过程

LCR 130. 衣橱整理 - 力扣(LeetCode)

这个比上一题简单啊,我草

class Solution {
    public int wardrobeFinishing(int m, int n, int cnt) {
        int res=0;
        boolean[][]visited=new boolean[m][n];
        return dfs(visited, m, n, cnt, 0, 0);
    }

    public int dfs(boolean[][]visited,int m,int n,int cnt,int i,int j)
    {
        if(i>=m||j>=n||sums(i)+sums(j)>cnt||visited[i][j])return 0;
        visited[i][j]=true;
        return 1+dfs(visited,m,n,cnt,i+1,j)+dfs(visited, m, n, cnt, i, j+1);
    }


    int sums(int x)
    {
        int s=0;
        while (x!=0) {
            s+=x%10;
            x=x/10;
        }
        return s;
    }
}

为什么这题不用回溯?就是不用啊


  目录