剑指offer算法题分析与整理(五)

下面整理一下我在刷剑指offer时,自己做的和网上大神做的各种思路与答案,自己的代码是思路一,保证可以通过,网友的代码提供出处链接。

目录

1、圆圈中最后剩下的数字(约瑟夫环)

n个数字(0,1,…,n-1)形成一个圆圈,从数字0开始,每次从这个圆圈中删除第m个数字(第一个为当前数字本身,第二个为当前数字的下一个数字)。当一个数字删除后,从被删除数字的下一个继续删除第m个数字。求出在这个圆圈中剩下的最后一个数字。
思路一:用数组模拟环

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        boolean[] flag=new boolean[n];//已经删除的要做标记
        int alive=n-1;
        int index=0;
        int c=0;
        while(alive>0){
            while(c!=m){
                if(!flag[index]){c++;}//删除过的就不理它
                if(index+1==n)index=0;
                else index++;
            }
            //将应该删除的点置true
            if(index==0)flag[n-1]=true;
            else flag[index-1]=true;
            alive--;
            c=0;
        }
        for(int i=0;i<n;i++){
            if(!flag[i])return i;
        }
        return -1;
    }
}

数组做标记到后期有很多无谓的判断,因为好多都已经标记为删除了,不是很好。

思路二:用链表模拟,真正的删除,时间和空间复杂度都是 O(n) O ( n )
来自牛客网@一神

import java.util.LinkedList;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        for (int i = 0; i < n; i ++) {
            list.add(i);
        }

        int bt = 0;
        while (list.size() > 1) {
            bt = (bt + m - 1) % list.size();
            list.remove(bt);
        }

        return list.size() == 1 ? list.get(0) : -1;
    }
}

思路三:推出递推公式。设A轮为:从n个人中删去下标为m-1的数,下一轮B:记下标为m的数下标为0,重新开始删除,假设从B轮开始最后留下的数下标是x,那么这个数在A轮该数的下标就是(x+m)%n
B—A

0— m
1—m+1
2—m+2
~~~
n-m-1—n-1
n-m—0
n-m+1—1
~~
n-2—m-2
n-1—m-1(A到B时已经删掉了)

所以最后一轮只有一个人的局,留下的是0,那么倒数第二轮有两个人的局,留下的是(0+m)%2,···,然后一直算到有n个人的局

public class Solution {
    public int LastRemaining_Solution(int n,int m) {
        if(n==0) return -1;

       int s=0;
       for(int i=2;i<=n;i++){
           s=(s+m)%i;
       }
       return s;
    }
}

2、对称的二叉树

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

思路一:层序遍历,遍历完每一层判断序列是否对称,如果子节点为空则标记为“#”

import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    boolean isSymmetrical(TreeNode pRoot){  
        if(pRoot==null)return true;
        Queue<TreeNode> q=new LinkedList<>();
        StringBuffer sb=new StringBuffer();
        q.add(pRoot);
        TreeNode t;
        while(!q.isEmpty()){

            int count=q.size();

            while(count-->0){
                t=q.poll();                
                if(t.left==null){sb.append("#");}
                else{q.offer(t.left);sb.append(t.left.val);}
                if(t.right==null){sb.append("#");}
                else{q.offer(t.right);sb.append(t.right.val);}
            }
            String s=sb.toString();
            sb=new StringBuffer();
            int m=0,n=s.length()-1;
            while(m<n){
                if(s.charAt(m)!=s.charAt(n))return false;
                m++;n--;
            }
        }
    return true;
    }
}

将根节点的左右子树看作两颗树,对这两棵树同步遍历(同时遍历镜像中的对应的点),边遍历边判断
来自牛客网@搬一块叫CV的砖

//层序,左树从左到右,右树从右到左
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if(root==NULL) return true;
        queue<TreeNode*> q1,q2;
        TreeNode *left,*right;
        q1.push(root->left);
        q2.push(root->right);
        while(!q1.empty() and !q2.empty()){
            left = q1.front();
            q1.pop();
            right = q2.front();
            q2.pop();
            //两边都是空
            if(NULL==left && NULL==right)
                continue;
            //只有一边是空
            if(NULL==left||NULL==right)
                return false;
            if (left->val != right->val)
                return false;
            q1.push(left->left);
            q1.push(left->right);
            q2.push(right->right);
            q2.push(right->left);
        }         
        return true;         
    }
};

来自牛客网@nino

//左树中序遍历,右树跟着左树镜像遍历
class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot){
        stack<TreeNode*> s1,s2;
        TreeNode *p1,*p2;
        p1=p2=pRoot;
        while((!s1.empty()&&!s2.empty())||(p1!=NULL&&p2!=NULL)){
            while(p1!=NULL&&p2!=NULL){
                s1.push(p1);
                s2.push(p2);
                p1=p1->left;
                p2=p2->right;
            }
            p1=s1.top();
            s1.pop();
            p2=s2.top();
            s2.pop();
            if(p1->val!=p2->val)
                return false;
            p1=p1->right;
            p2=p2->left;
        }
        if(!s1.empty()||!s2.empty())
            return false;
        if(p1!=NULL||p2!=NULL)
            return false;
        return true;
    }
};

思路二:只用一个栈或者队列,将镜像中对应的点,即一个对子,同时入,并且同时出
来自牛客网@华科渣硕

//DFS
boolean isSymmetricalDFS(TreeNode pRoot){
        if(pRoot == null) return true;
        Stack<TreeNode> s = new Stack<>();
        s.push(pRoot.left);
        s.push(pRoot.right);
        while(!s.empty()) {
            TreeNode right = s.pop();//成对取出
            TreeNode left = s.pop();
            if(left == null && right == null) continue;
            if(left == null || right == null) return false;
            if(left.val != right.val) return false;
            //成对插入
            s.push(left.left);
            s.push(right.right);
            s.push(left.right);
            s.push(right.left);
        }
        return true;
    }
//BFS
boolean isSymmetricalBFS(TreeNode pRoot){
        if(pRoot == null) return true;
        Queue<TreeNode> s = new LinkedList<>();
        s.offer(pRoot.left);
        s.offer(pRoot.right);
        while(!s.isEmpty()) {
            TreeNode right = s.poll();//成对取出
            TreeNode left = s.poll();
            if(left == null && right == null) continue;
            if(left == null || right == null) return false;
            if(left.val != right.val) return false;
            //成对插入
            s.offer(left.left);
            s.offer(right.right);
            s.offer(left.right);
            s.offer(right.left);
        }
        return true;
    }

思路三:递归
来自牛客网@Aurora1

boolean isSymmetrical(TreeNode pRoot) {
        if (pRoot == null)return true; 
        return f(pRoot.left,pRoot.right);
    }

    boolean f(TreeNode t1, TreeNode t2) {
        if (t1 == null && t2 == null)
            return true;

        if (t1 != null && t2 != null)
            return t1.val == t2.val && f(t1.left,t2.right) && f(t1.right, t2.left);

        return false;
    }

3、把二叉树打印成多行

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

思路一:对每层的节点计数

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer> > AA=new ArrayList<ArrayList<Integer>>();
        if(pRoot==null)return AA;
        Queue<TreeNode> q=new LinkedList<>();
        TreeNode t;

        q.offer(pRoot);
        int count,c;
        ArrayList<Integer> A=new ArrayList<>();
        while(!q.isEmpty()){
            count=q.size();
            c=0;
            while(c++<count){
                if(q.peek().left!=null)q.offer(q.peek().left);
                if(q.peek().right!=null)q.offer(q.peek().right);
                A.add(q.peek().val);
                q.poll(); 
            }
            //这里如果直接把A添加进去,clear之后AA里面的也空了
            AA.add(new ArrayList<>(A));
            A.clear();
        }
        return AA;
    }
}

思路二:递归
来自牛客网@spursKawhi

//用递归做的
public class Solution {
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> list = new ArrayList<>();
        depth(pRoot, 1, list);
        return list;
    }

    private void depth(TreeNode root, int depth, ArrayList<ArrayList<Integer>> list) {
        if(root == null) return;
        if(depth > list.size())//先添加个空A进去,再将同一层的节点加入这个A中
            list.add(new ArrayList<Integer>());
        list.get(depth -1).add(root.val);

        depth(root.left, depth + 1, list);
        depth(root.right, depth + 1, list);
    }
}

4、左旋转字符串

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。
思路一 YX=(XTYT)T Y X = ( X T Y T ) T

public class Solution {
    public String LeftRotateString(String str,int n) {
        if(str==null||str.length()==0||n==0)return str;
        char[] chars = str.toCharArray();        
        n%=chars.length;
        reverse(chars, 0, n-1);
        reverse(chars, n, chars.length-1);
        reverse(chars, 0, chars.length-1);

        return new String(chars);
    }

    public void reverse(char[] chars,int low,int high){
        char temp;
        while(low<high){
            temp = chars[low];
            chars[low] = chars[high];
            chars[high] = temp;
            low++;
            high--;
        }
    }
}

思路二:来自牛客网@OutOfBoundsException
类似于java的Collections的rotate方法的实现: Collections的rotate有两种实现:

  1. 对于类似数组这种随机访问的数据结构,采用“递推”的思想
  2. 对于类似于链表的数据结构,采用“链表反转”的思想

本题可看做是第一种,因此采用递推的思想:

public class Solution {
    //rotation
    public String LeftRotateString(String str,int n) {
        int len = str.length();
        if (n == 0 || len == 0) return str;
        n %= len;
        char[] ch = str.toCharArray();
        int doneCnt = 0, i = 0;
        while (doneCnt < len && i < len) {
            int j = i++;
            char cur = ch[j];//cur与 旋转后它应该在的位置 上的数比较,不同就交换,相同则cur已经在旋转后的位置上了
            while (cur != ch[j=(j-n+len)%len]) { 
                char tmp = ch[j]; ch[j] = cur; cur = tmp; //exchange
                doneCnt++;
            }
        }
        return new String(ch);
    }
}

关于java Collections的rotate方法的源码分析

5、链表中的环

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

思路一

  • 从起点快慢指针起步,相遇则有环,否则无环;
  • 从相遇点对开始计数,快慢指针再次相遇(还是在相遇点)则,计数就是环的大小;
  • 一个从起点,一个从相遇点,同速运动,相遇处即是入口。或者已知环的大小,两个指针同时从起点出发,一个指针等另一个先走环的大小的步数,再走,两者相遇即是入口处。
/** public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } */
public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead){
        if(pHead==null||pHead.next==null)return null;
        ListNode fast=pHead;
        ListNode slow=pHead;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(fast==slow)break; //异速指针相遇 
        }
        if(fast!=slow)return null;//没有环
        slow=pHead;
        while(fast!=slow){//一个从相遇点,一个从起点,同速运动
            fast=fast.next;
            slow=slow.next;
        }
        return fast;        
    }
}

思路二:断链法,破坏了链表结构,不推荐。
来自牛客网@_冬_至_

/** 时间复杂度为O(n),两个指针,一个在前面,另一个紧邻着这个指针,在后面。 两个指针同时向前移动,每移动一次,前面的指针的next指向NULL。 也就是说:访问过的节点都断开,最后到达的那个节点一定是尾节点的下一个, 也就是循环的第一个。 这时候已经是第二次访问循环的第一节点了,第一次访问的时候我们已经让它指向了NULL, 所以到这结束。 */
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead){
        if (!pHead->next)
            return NULL;
        ListNode* previous = pHead;
        ListNode* front = pHead ->next;
        while (front){
            previous->next = NULL;
            previous = front;
            front = front->next;
        }
        return previous;
    }
};

6、浮点数的整数次方

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

思路一:快速幂

public class Solution {
    public double Power(double base, int exponent) {
        double result=1;
        int i=0;
        boolean flag=false;
        if(exponent<0){exponent=-exponent;flag=true;}
        while(i<32){
            //if((exponent&(1<<i))==1) //这是错的=_=!
            if((exponent>>i&1)==1){
                result*=base;
            }
            i++;
            if((1<<i)>exponent)break;
            base*=base;
        }
        return  flag?1/result:result;
    }
}

上面我写的很乱,而且没有判断base是不是等于0,下面
来自牛客网@hey读行

//快速幂
   public double Power(double base, int exponent) {
        if (exponent == 0) {
            return 1.0;
        }
        if (base - 0.0 == 0.00001 || base - 0.0 == -0.00001)  {
            if (exponent < 0) {
                throw new RuntimeException("除0异常"); 
            }else{
                return 0.0;
            }
        }
        int e = exponent > 0 ? exponent: -exponent;
        double res = 1;
        while (e != 0) {//等于0就停止循环
            res = (e & 1) != 0 ? res * base : res;
            base *= base;
            e = e >> 1;
        }
        return exponent > 0 ? res : 1/res;
  }

思路二:递归,分奇数偶数

// 递归
    public double Power(double base, int exponent) {
        if (exponent == 0) {
            return 1.0;
        }
        if (base - 0.0 == 0.00001 || base - 0.0 == -0.00001)  {
            if (exponent < 0) {
                throw new RuntimeException("除0异常"); 
            }else{
                return 0.0;
            }
        }
        return exponent > 0 ? getPower(base, exponent) : 1/getPower(base, -exponent);
    }

    public static double getPower(double base, int e) {
        if (e == 1) {
            return base;
        }
        double halfPower = getPower(base, e >> 1);//e右移一位,e是奇数则结果是(e-1)/2,e偶数则是e/2
        return (e & 1) != 0 ? base * halfPower * halfPower : halfPower * halfPower;
    }

7、包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
思路:用空间换时间,需要辅助栈存最小数。

//辅助栈的高度与存储栈的一样
import java.util.Stack;

public class Solution {
    Stack<Integer> s=new Stack<>();
    Stack<Integer> sAssist=new Stack<>();

    public void push(int node) {
        s.push(node);
        if(!sAssist.isEmpty()&&sAssist.peek()<node)sAssist.push(sAssist.peek());
        else sAssist.push(node);
    }

    public void pop() {
        s.pop();
        sAssist.pop();
    }

    public int top() {
        return s.peek();
    }

    public int min() {
        return sAssist.peek();
    }
}

考虑每次入栈的时候,如果入栈的元素比min中的栈顶元素小或等于则入栈,否则不如栈。比如,
data中依次入栈,5, 4, 3, 8, 10, 11, 12, 1
则min依次入栈,5, 4, 3,no,no, no, no, 1
但是这种方法在输入有多个重复最小值时,min栈出去一个最小值就没有这个值了,所有这种方法不太好。

8、旋转数组的最小数字

输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
思路:注意是非递减数列,可能有重复的。AB,A<=B,BA,最小值就是A部分的第一个值

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array==null||array.length==0)return 0;
        int s=0,e=array.length-1,mid;
        while(s<e){//始终保证最小值在array[s]~array[e]里面
            mid=(s+e)/2;
            if(array[s]<array[e]){//递增序列,第一个就是最小值
                return array[s];               
            }
            else if(array[s]==array[e]){//头尾相等
                if(array[mid]<array[s])e=mid;//说明mid在后一个非递减部分,那么mid有可能是最小值,所以这里不能e=mid-1
                else if(array[mid]>array[s])s=mid+1;//说明mid在前一个非递减部分,最小值在mid的右边,s=mid+1
                else s++;//不知道mid在前半部分还是后半部分,如22222212,22122222,我只能判定一个边界s++肯定不影响找到最小值
            }
            else{//头大于尾
                if(array[mid]>=array[s]){s=mid+1;}//mid在前面一个序列
                else{ e=mid;}
            }      
        }
         return array[s];      
    }
}

9、两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共结点。

思路一:最开始我想的是,将其中一条链变成环,知道环的长度,然后从另一条链的起点开始,一个指针先走环的长度的步数,另一个指针再走,两指针相遇的地方就是环的入口,也就是第一个公共节点。

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p=pHead1;
        int c=1;
        while(p.next!=null){
            p=p.next;
            c++;
        }
        p.next=pHead1;
        ListNode p1=pHead2;
        ListNode p2=pHead2;
        while(c!=0){
            p1=p1.next;
            c--;
        }

        while(p1!=null){
            p1=p1.next;
            p2=p2.next;
            if(p1==p2)return p1;
        }
        return null;
    }
}

但是这代码一直显示循环超时,原来是我改变了链表的结构,牛客网打印返回的链表,从第一个公共节点开始往后是一个环,所以这个链表打印不出来。实际上是没必要更改链表结构的,要实现环的效果,手动将指针从尾部跳到头部就好了。

/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
            if(pHead1==null||pHead2==null)return null;
            ListNode p=pHead1;
            //找到pHead1的长度,即环的长度
            int c=1;
            while(p.next!=null){       
                p=p.next;
                c++;
            }
            //两个指针从pHead2的起点出发
            ListNode p1=pHead2;
            ListNode p2=pHead2;
            for(;c>0;c--){    //p1先走环的长度,手动连接环的头尾
                p1=p1.next==null?pHead1:p1.next;
            }
            //如果p2为空表示这两个链表根本就没有相交
            while(p2!=null){
                if(p1==p2)return p1;
                p1=p1.next==null?pHead1:p1.next;
                p2=p2.next;  
            }
        return null;
    }
}

思路二:找到两个链表长度之差,从长的链表的起点开始,一个指针先走长度之差,另一个指针从短的链表开始,相遇点就是两个链表的交点。
来自牛客网@赵振江_12浪潮优派

public ListNode FindFirstCommonNodeII(ListNode pHead1, ListNode pHead2) {
        ListNode current1 = pHead1;// 链表1
        ListNode current2 = pHead2;// 链表2
        if (pHead1 == null || pHead2 == null)
            return null;
        int length1 = getLength(current1);
        int length2 = getLength(current2);
        // 两连表的长度差

        // 如果链表1的长度大于链表2的长度
        if (length1 >= length2) {
            int len = length1 - length2;
            // 先遍历链表1,遍历的长度就是两链表的长度差
            while (len > 0) {
                current1 = current1.next;
                len--;
            } 
        }
        // 如果链表2的长度大于链表1的长度
        else if (length1 < length2) {
            int len = length2 - length1;
            // 先遍历链表1,遍历的长度就是两链表的长度差
            while (len > 0) {
                current2 = current2.next;
                len--;
            } 
        }
        //开始齐头并进,直到找到第一个公共结点
        while(current1!=current2){
            current1=current1.next;
            current2=current2.next;
        }
        return current1; 
    }

    // 求指定链表的长度
    public static int getLength(ListNode pHead) {
        int length = 0;
        ListNode current = pHead;
        while (current != null) {
            length++;
            current = current.next;
        }
        return length;
    }

思路三:一个链表是AN,一个是BN,一个指针从A开始到链表尾部后跳到B,一个指针从B开始到链表尾部后跳到A,最终在交点处相遇,每个指针都走了A+B+N的距离。不过如果两个链表没有交点,下面的循环不会结束。
来自牛客网@selfboot

class Solution { public: ListNode* FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2) { ListNode *p1 = pHead1; ListNode *p2 = pHead2; while(p1!=p2){ p1 = (p1==NULL ? pHead2 : p1->next); p2 = (p2==NULL ? pHead1 : p2->next); } return p1; } };

10、从1到N整数中1出现的次数

如1~13中包含1的数字有1、10、11、12、13。因此共出现6次。
思路一:递归

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        if(n==0)return 0;
        int x=1;
        while(n/(10*x)!=0)x*=10;
        //x为与N同位数的最小数,如n=134,x=100
        if(x==1)return 1;//说明n为1到9
        int a=n/x;//最高位上的数,如n=134,a=1
        int b=n%x;//最高位后面的数,如n=134,b=34
        // b里面含有1的个数+a*(1到x-1中含有1的个数)+最高位为1的个数
        return f(b,x/10)+a*f(x-1,x/10)+(a>1?x:b+1);
    }
    //为了不用每次都循环找x
    int f(int n,int x){
        if(n==0)return 0;
        if(x==1)return 1;
        int a=n/x;
        int b=n%x;
        //a可能为0
        int c;
        if(a>1)c=x;
        else if(a==1)c=b+1;
        else c=0;

        return f(b,x/10)+a*f(x-1,x/10)+c;
    }
}

思路二:待更新

点赞