• 剑指offer难题


    链表

    1. 链表中环的入口结点
    • 快慢指针,等快慢指针相遇后,将慢指针重置于链表头结点,再将快慢指针都以相同步长(1)向后走,相遇得到的节点为所求节点
    • 空指针:当快指针先走到空指针时,判定无环,退出循环
    1. 复杂链表的复制
    • 哈希表,遍历一遍该链表,创建所有碰到的节点,用哈希表存储next关系
    • 遍历两遍(简单方法)

    二叉树

    1. 二叉树的下一个结点
    • 给定一个节点,求其在中序遍历中的下一个节点(已知二叉树存在父节点指针)
    • 如果给定节点有右子树,则在右子树中找到最左边的节点作为返回结果
    • 如果没有右子树,则返回其父节点
    • 如果是根节点,且没有右子树,返回空
    1. 序列化二叉树
      空节点也需要被序列化
    /*
    public class TreeNode {
        int val = 0;
        TreeNode left = null;
        TreeNode right = null;
    
        public TreeNode(int val) {
            this.val = val;
        }
    }
    */
    import java.util.*;
    public class Solution {
        String Serialize(TreeNode root) {
            if(root == null) return "" ;
            StringBuilder builder = new StringBuilder();
            dfs(root,builder);
            return builder.toString();   
        }
        
        void dfs(TreeNode root, StringBuilder builder){
            if(root==null){
                builder.append("#,");
                return;
            }
            builder.append(root.val).append(",");
            dfs(root.left, builder);
            dfs(root.right, builder);
        }
        
        TreeNode Deserialize(String str) {
            if(str.length() == 0) return  null;
            String[] split = str.split(",");
            Queue<String> queue = new LinkedList<>();
            for(String s : split){
                queue.offer(s);
            }
            return dfs(queue);
        }
        
        TreeNode dfs(Queue<String> que){
            if(que.isEmpty()){
                return null;
            }
            String head = que.poll();
            if("#".equals(head)){
                return null;
            }
            TreeNode root = new TreeNode(Integer.parseInt(head));
            root.left = dfs(que);
            root.right = dfs(que);
            return root;
        }
        
    }
    

    二叉搜索树

    1. 二叉树树的后序遍历序列
      给定一个序列,判断其是否为二叉搜索树的后序遍历序列。对子树进行递归判断。
    public class Solution {
        public boolean VerifySquenceOfBST(int [] sequence) {
            if(sequence ==null || sequence.length == 0) return false;
            return rec(sequence, 0, sequence.length-1);
        }
        
        private boolean rec(int[] seq, int start, int root){
            if(start >= root )return true;
            int left_start;
            for(left_start=start; left_start<root; left_start++){
                if(seq[left_start] > seq[root]){
                    break;
                }
            }
            //判断另一子树(右子树)是否满足“值都小于root”的条件
            for(int i=left_start; i<root; i++){
                if(seq[i] < seq[root]){
                    return false;
                }
            }
            return rec(seq, start, left_start-1) && rec(seq, left_start, root-1);
        }
    }
    
    1. 二叉搜索树与双向链表
      维护pre节点和头结点root,中序遍历二叉树
    public class Solution {
        TreeNode pre = null;
        TreeNode root = null;
    
        public TreeNode Convert(TreeNode pRootOfTree) {
            if(pRootOfTree == null) return null;
            Convert(pRootOfTree.left);
            if(root==null){
                root = pRootOfTree;
            }
            if(pre!=null){
                pRootOfTree.left = pre;
                pre.right = pRootOfTree;
            }
            pre = pRootOfTree;
            Convert(pRootOfTree.right);
            return root;
        }
    }
    
    1. 二叉搜索树的第k个结点
      维护一个计数值k,采用先右后左的中序遍历顺序,每遍历一个节点则将k减1,k为0时则为所求节点
    public class Solution {
        private int k = 0;
        private TreeNode res = null;    
        TreeNode KthNode(TreeNode pRoot, int k) {
            if(pRoot == null || k ==0) return null;
            this.k = k;
            inOrder(pRoot);
            return res;
        }
        
        void inOrder(TreeNode r){
            if(r == null) return ;
            inOrder(r.left);
            if(--k == 0){
                res = r;
            } 
            inOrder(r.right);
        }
    }
    

    数组

    1. 旋转数组的最小数字
      需要实现的是“最小数字”的查找。
      实现O(n)时间内的查找很容易,但需要采用二分查找实现O(logn)时间内的查找。
    public class Solution {
        public int minNumberInRotateArray(int[] array) {
            int i = 0, j = array.length - 1;
            while (i < j) {
                if (array[i] < array[j]) {
                    return array[i];
                }
                int mid = (i + j) >> 1;
                if (array[mid] > array[i]) {
                    i = mid + 1;
                } else if (array[mid] < array[j]) {
                    j = mid; // 如果是mid-1,则可能会错过最小值,因为找的就是最小值
                } else i++;  // 巧妙避免了offer书上说的坑点(1 0 1 1 1)
            }
            return array[i];
        }
    }
    
    1. 数组中出现次数超过一半的数字
      使用count变量记录result出现的次数。遍历数组,当遇到与之前的result相同的数字,则将count加1,;否则减1。count为0时更改result,否则更新count。
    public class Solution {
        public int MoreThanHalfNum_Solution(int [] array) {
            int result= 0, count = 0;
            for(int i=0; i< array.length; i++){
                if(count == 0){
                    result= array[i];
                    count ++;
                }
                else if(count > 0){
                    if(array[i]!=result){
                        count -- ;
                    }else{
                        count++;
                    }
                }
            }
            //验证是否出现了大于n/2次
            count = 0;
            for(int i=0; i< array.length; i++){
                if(array[i] == result){
                    count ++;
                }
            }
            return count>(array.length/2)?result:0;
        }
    }
    
    1. 数组中的逆序对
      采用归并排序的思路来处理。
      逆序对的总数 = 左边数组中的逆序对的数量 + 右边数组中逆序对的数量 + 左右结合成新的顺序数组时中出现的逆序对的数量

    2. 数字在排序数组中出现的次数

    public class Solution {
        public int GetNumberOfK(int [] array , int k) {
            if(array == null || array.length == 0) return 0;
            int r = rightMost(array, k); 
            int l = leftMost(array, k);
            if (r !=-1 && l != -1 ) return r-l+1;
            return 0;
        }
        
        private int rightMost (int [] array, int k){
            int len = array.length;
            int left = 0, right = len-1;
            while(left<right){
                int mid = left + (right - left+1)/2;
                if(array[mid] > k){
                    right = mid - 1;
                }else{
                    left = mid;
                }
            }
            if(left >= 0 && left < len && array[left] == k){
                return left;    
            }
            return -1;
        }
        
         private int leftMost (int [] array, int k){
            int len = array.length;
            int left = 0, right = len-1;
            while(left<right){
                int mid = left + (right - left)/2;
                if(array[mid] < k){
                    left = mid + 1;
                }else{
                    right = mid;
                }
            }
            if(left >= 0 && left < len&& array[left] == k){
                return left;    
            }
            return -1;
        }
    }
    

    字符串

    1. 字符串的排列
      回溯加哈希表。若是要字典序,则先把字符数组进行排序。

    2. 把字符串转换成整数
      自动机或者标记法遍历

    53.表示数值的字符串
    自动机或者标记法遍历

    1. 正则表达式匹配
      动态规划,递归

    1. 栈的压入、弹出序列
    import java.util.ArrayList;
    import java.util.Stack;
    public class Solution {
        public boolean IsPopOrder(int [] pushA,int [] popA) {
            Stack<Integer> stack = new Stack<>();
            int len = popA.length;
            for(int i=0, j=0; i<len; i++ ){
                stack.push(pushA[i]);
                while(!stack.isEmpty() && stack.peek()==popA[j]){
                    stack.pop();
                    j++;
                }
            }
            return stack.isEmpty();
        }
    }
    

    递归

    1. 变态跳台阶
      一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
      规律: f(n)=2^(n-1)

    回溯

    1. 矩阵中的路径
    import java.util.*;
    public class Solution {
        public boolean hasPath (char[][] matrix, String word) {
            //循环多次找到首个字母的初始位置
            for(int i=0;i<matrix.length;i++){
                for(int j=0;j<matrix[0].length;j++){
                    if(matrix[i][j]==word.charAt(0)){
                        //找到初始位置,设置访问标记数组,可能不是一个,所有每次都创建新数组
                        boolean vis[][]=new boolean[matrix.length][matrix[0].length];
                        //dfs传入初始位置,访问数组,矩阵,寻找的字符位置,字符串
                        if(dfs(i,j,vis,matrix,0,word)==true)return true;
                    }
                }
            }
            return false;
        }
        boolean dfs(int m,int n,boolean[][] vis,char[][] matrix,int k,String word){
            //如果m,n位置在矩阵外返回false,如果这一节点走过返回false
            if(m<0||n<0||m>=matrix.length||n>=matrix[0].length||vis[m][n]==true)return false;
            //如果这个位置字母和字符串尾字母一样,且字符串序号就是最后一个,代表遍历完了,返回true,不在遍历
            if(word.charAt(word.length()-1)==matrix[m][n]&&(k==word.length()-1))return true;
            //字符串字母是序号对应字母
            if(word.charAt(k)==matrix[m][n]){
                //访问过了
                vis[m][n]=true;
                //如果上下左右有下一个字母(k+1)则继续遍历,否则访问设置为未访问(每个字母这能经过一次,没有找到下一个字母,所以其实没有访问过)
                if(dfs(m+1,n,vis,matrix,k+1,word)||dfs(m,n+1,vis,matrix,k+1,word)||dfs(m,n-1,vis,matrix,k+1,word)||dfs(m-1,n,vis,matrix,k+1,word)){
                    return true;
                }else{
                    vis[m][n]=false;
                    return false;
                }
            }else{  
                //当前不是对应字母,返回false
                vis[m][n]=false;
                return false;
            }
        }
    }
    

    排序算法及应用

    1. 最小的K个数
      快速排序或者堆排序
    import java.util.ArrayList;
    
    public class Solution {
        public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
            ArrayList<Integer> res = new ArrayList<>();
            if(input==null || input.length==0 || k == 0 || k>input.length){
                return res;
            }
            int left = 0;
            int right = input.length-1;
            int j = quicksort(input, left, right);
            while(j+1 !=k){
                if(j+1<k){
                    left = j+1;
                }else{
                    right = j;
                }
                j = quicksort(input, left, right);
            }
            for(int i=0; i<=j; i++){
                res.add(input[i]);
            }
            return res;
        }
        
        public int quicksort(int [] input, int low, int high){
            int pivot = input[low];
            int i = low, j = high;
            while(i<j){
                while(i<j&&input[j]>pivot){--j;}
                while(i<j&&input[i]<pivot){++i;}
                
                if(i>=j) break;
                
                int tmp = input[i];
                input[i] = input[j];
                input[j] = tmp;
                ++i;
                --j;
            }
            return i;
        }
    }
    
    1. 数据流中的中位数
      两个堆:大根堆保存较小的一半数,小根堆保存较大的一半数
    import java.util.PriorityQueue;
    import java.util.Comparator;
    public class Solution {
        //小顶堆,用该堆记录位于中位数后面的部分
        private PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
    
        //大顶堆,用该堆记录位于中位数前面的部分
        private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(15, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
    
        //记录偶数个还是奇数个
        int count = 0;
        //每次插入小顶堆的是当前大顶堆中最大的数
        //每次插入大顶堆的是当前小顶堆中最小的数
        //这样保证小顶堆中的数永远大于等于大顶堆中的数
        //中位数就可以方便地从两者的根结点中获取了
        //优先队列中的常用方法有:增加元素,删除栈顶,获得栈顶元素,和队列中的几个函数应该是一样的
        //offer peek poll,
        public void Insert(Integer num) {
            //个数为偶数的话,则先插入到大顶堆,然后将大顶堆中最大的数插入小顶堆中。因为我们在取数的时候假定的时候当奇数的时候从小顶堆中取数据,偶数的时候从两个顶部取数据。所以当当前个数为偶数是,其实预期要将数字放在小顶堆,所以先放在大顶堆,然后再放到小顶堆。当前个数为奇数的时候是同样的道理。
            if(count % 2 == 0){
                maxHeap.offer(num);
                int max = maxHeap.poll();
                minHeap.offer(max);
            }else{
                //个数为奇数的话,则先插入到小顶堆,然后将小顶堆中最小的数插入大顶堆中
                minHeap.offer(num);
                int min = minHeap.poll();
                maxHeap.offer(min);
            }
            count++;
        }
        public Double GetMedian() {
            //当前为偶数个,则取小顶堆和大顶堆的堆顶元素求平均
            if(count % 2 == 0){
                return new Double(minHeap.peek() + maxHeap.peek())/2;
            }else{
                //当前为奇数个,则直接从小顶堆中取元素即可,所以我们要保证小顶堆中的元素的个数。
                return new Double(minHeap.peek());
            }
        }
    }
    

    双指针法

    1. 和为S的连续正数序列
      设定两个指针,一个指向第一个数,一个指向最后一个数,在此之前需要设定第一个数和最后一个数的值,由于是正数序列,所以可以把第一个数设为1,最后一个数为2(因为是要求是连续正数序列,最后不可能和第一个数重合)。下一步就是不断改变第一个数和最后一个数的值,如果从第一个数到最后一个数的和刚好是要求的和,那么把所有的数都添加到一个序列中;如果大于要求的和,则说明从第一个数到最后一个数之间的范围太大,因此减小范围,需要把第一个数的值加1,同时把当前和减去原来的第一个数的值;如果小于要求的和,说明范围太小,因此把最后一个数加1,同时把当前的和加上改变之后的最后一个数的值。这样,不断修改第一个数和最后一个数的值,就能确定所有连续正数序列的和等于S的序列了。

    2. 滑动窗口的最大值

    import java.util.*;
    public class Solution {
        public ArrayList<Integer> maxInWindows(int [] num, int size) {
            ArrayList<Integer> res = new ArrayList<>();
            if (num == null || num.length ==0 || size ==0 ||size > num.length) return res;
            int len = num.length;
            Deque<Integer> deque = new ArrayDeque<>();
            for(int i = 0, j = 1 - size; i < len; i++, j++) {
                // 删除 deque 中对应的 nums[i-1]
                if(j > 0 && deque.peekFirst() == num[j - 1]){
                    deque.removeFirst();
                }
                
              // 保持 deque 递减(把小于当前数的队列元素出队)
                while(!deque.isEmpty() && deque.peekLast() < num[i]){
                    deque.removeLast();
                }
    
                deque.addLast(num[i]);
    
                // 记录窗口最大值
                if(j >= 0){
                    res.add(deque.peekFirst());
                }
            }
            return res;
        }
    }
    

    其他

    1. 扑克牌顺子
    import java.util.Arrays;
    public class Solution {
        public boolean IsContinuous(int [] numbers) {
            if(numbers == null || numbers.length == 0) return false;
            Arrays.sort(numbers);
            int len = numbers.length;
            int i =0;
            for(int j = 0; j< len; j++){
                if(numbers[j] == 0){
                    ++i;
                    continue;
                }
                if(j+1<len && numbers[j] == numbers[j+1]) return false;
            }
            return (numbers[len-1] - numbers[i]) <5;
        }
    }
    
    1. 约瑟夫环
      递推公式: f(i)表示i个人玩报m退出的最终胜利者
    • f(1) = 0;
    • f(i) = (f(i-1) + m) % i;
      迭代解法:
    int LastRemaining_Solution(int n, int m)
    {
      if(n < 1 || m < 1){
        return -1;
      }
      int last = 0;
      for(int i = 2; i <= n; i++){
          last = (last + m) % i;
      }
      return last;
    }
    
    1. 求1+2+...+n
    public class Solution {
        public int Sum_Solution(int n) {
            int res = n;
            boolean a = (res==0) || ((res += Sum_Solution(n-1)) == 0);
            return res;
        }
    }
    
    1. 不用加减乘除的加法
      三步走的方式计算二进制值相加: 101,111
    • 第一步:相加各位的值,不算进位,得到010,二进制每位相加就相当于各位做异或操作,101^111。
    • 第二步:计算进位值,得到1010,相当于各位做与操作得到101,再向左移一位得到1010,(101&111)<<1。
    • 第三步:重复上述两步, 各位相加 010^1010=1000,进位值为100=(010&1010)<<1。
    • 继续重复上述两步:1000^100 = 1100,进位值为0,跳出循环,1100为最终结果。
    public class Solution {
        public int Add(int num1,int num2) {
            int a = num1 ^ num2;
            int b = (num1 & num2)<<1;
            while(b!=0){
                int tmp = a ^ b;
                b = (a & b) << 1;
                a = tmp;
            }
            return a;
        }
    }
    
  • 相关阅读:
    Beginning SDL 2.0(5) 基于MFC和SDL的YuvPlayer
    Beginning SDL 2.0(6) 音频渲染及wav播放
    Beginning SDL 2.0(4) YUV加载及渲染
    Beginning SDL 2.0(3) SDL介绍及BMP渲染
    获取windows可执行文件的version信息(版本号)
    visual studio 2005提示脚本错误 /VC/VCWizards/2052/Common.js
    Beginning SDL 2.0(2) TwinklebearDev SDL 2.0 Tutorial
    Beginning SDL 2.0(1) SDL功能简介
    ffmpeg与H264编码指南
    2015-07学习总结——网络编程(TCP/IP)
  • 原文地址:https://www.cnblogs.com/bacmive/p/14955524.html
Copyright © 2020-2023  润新知