剑指Offer刷题

这篇博客主要介绍了《剑指Offer》中的数据结构和算法题目,涵盖数字与矩阵、栈队列堆、双指针、链表、树等主题。包括顺时针打印矩阵、快速选择、字符流中第一个不重复的字符、链表反转、二叉树的镜像、贪心算法和位运算等多个经典问题的解法。
摘要由CSDN通过智能技术生成

数字与矩阵

数组中重复的数字

//时间复杂度 O(n) 空间复杂度O(1)
import java.util.*;
public class Solution {
    public int duplicate (int[] numbers) {
        /*
        不合法
        (1)长度为0
        (2)内容不再0到n-1之间
        */
        if(numbers.length == 0)
            return -1;
        for(int i = 0;i < numbers.length;i++)
            if(numbers[i] >= numbers.length || numbers[i] < 0)
                return -1;
        /*
        合法输入
        1、其中有重复元素
        对每一个位置上的值进行核验,直到其中的值与下标值相等则检查下一位置
        核验过程中将当下位置上的值交换给与该值一致的下标对应位置
        (1)若要交换的目标位置其中值已于下标一致说明重复,返回该值
         (2)若不一致,则进行交换
        2、其中没有重复元素
        */
        for(int i = 0 ; i < numbers.length ; i++){
            while(numbers[i] != i){
                if(numbers[numbers[i]] == numbers[i])
                    return numbers[i];
                int temp = numbers[numbers[i]];
                numbers[numbers[i]] = numbers[i];
                numbers[i] = temp;
            }
        }
        return -1; 
    }
}

二维数组中的查找

public class Solution {
    public boolean Find(int target, int [][] array) {
        //不合法
        if(array == null)
            return false;
        /*
        合法
        (1)存在
        从右上角开始搜查
        target比当前大向下搜查
        target比当前小向左搜查
        (2)不存在
        */
        int row = 0, column = array[0].length-1;
        while(row < array.length && column >= 0){
            if(array[row][column] > target)
                column--;
            else if(array[row][column] < target)
                row++;
            else
                return true;
        }
        return false;
    }
}

替换空格

//时间复杂度O(n)
import java.util.*;
public class Solution {
    public String replaceSpace (String s) {
        //不合法
        if(s.isEmpty())
            return s;
        /*
        合法
        p1记下原字符串末尾位置
        对原字符串进行搜查,遇到一个空格则在之后追加两个空格(空格位置改为%20,相较之前多了两个字符,故遇到一个空格要追加两个)
        当p1指向为空格时,p2依次向前填充上0 2 %
        当p1指向不为空格时,p2赋值为p1指向字符
        */
        int p1 = s.length() - 1;
        StringBuffer str = new StringBuffer(s);
        for(int i = 0 ;i < s.length() ; i++)
            if(s.charAt(i) == ' ')
                str.append("  ");
        
        int p2 = str.length() - 1;
        while(p1>=0){
            if(str.charAt(p1) == ' '){
                str.setCharAt(p2--,'0');
                str.setCharAt(p2--,'2');
                str.setCharAt(p2--,'%');
            }
            else{
                str.setCharAt(p2--,str.charAt(p1));
            }
            p1--;
        }
      
        return str.toString();
    }
}

(***)顺时针打印矩阵

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer>  array = new ArrayList<>();
        //不合法
        if(matrix == null)
            return array;
        int row = matrix.length;
        int column = matrix[0].length;
        /*
        圈数取决于行和列中比较小的值
        若为奇数则减1除2再加一为总圈数
        若为偶数先减1导致除2后结果少了1,再加上即为所求
        
        */
        int circle = ((row < column?row:column)-1)/2+1;
        for(int i = 0;i < circle; i++){
            //上
            for(int j = i; j < column - i;j++)
                array.add(matrix[i][j]);
            //右
            for(int j = i+1; j < row - i; j++)
                array.add(matrix[j][column-i-1]);
            //下,在进行是要看上和下是否重合重合则不必输出
            for(int j = column - i-2; (j >= i)&&(i!=row-i-1); j--)
                array.add(matrix[row-i-1][j]);
            //左,在进行是要看左和右是否重合重合则不必输出
            for(int j = row - i - 2; (j > i)&&(i!=column-i-1); j--)
                array.add(matrix[j][i]);
        }
        return array;
    }
}

第一个只出现一次的字符

import java.util.HashMap;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        if(str.isEmpty())
           return -1; 
        //因为map是无序的故要按照str中元素的顺序在遍历一遍才能找到最先出现的
        HashMap<Character,Integer> map = new HashMap<Character,Integer>();
        for(int i = 0; i < str.length(); i++){
            /*
            查看map是否包含该key,若包含则新得value会替换旧的
            */
            if(map.containsKey(str.charAt(i))){
                map.put(str.charAt(i),1);
            }else{
                map.put(str.charAt(i),0);
            }
        }
        for(int i = 0; i < str.length(); i++){
            if(map.get(str.charAt(i))==0)
                return i;
        }
        return -1;
    }
}

栈队列堆

用两个栈实现队列

import java.util.Stack;
public class Solution {
    /*
    入队:直接入stack1
    出队:若stack2为空则stack1依次弹栈入stack2
        然后将stack2栈顶弹出
    */
    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.empty()){
            while(!stack1.empty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

两个队列模拟一个栈
入栈:直接入队列1
出栈:将队列1中元素依次出队入队列2,最后一个元素作为出栈元素

栈的压入、弹出序列

public class Solution {
    /*
    从pushA的第一个元素开始,在分别指向pushA和popA的下标不超过长度的情况下
    每次从pushA压一个元素入栈
    如果当前栈顶元素与popindex指向元素一致则弹栈popindex++,直到栈顶与popA指向元素不同
    (这里注意比较栈顶与popindex指向元素时,要保证栈不为空)
    
    全部进行完后,若栈内为空说明popA与pushA匹配
    否则不匹配
    */
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        if(pushA == null || popA == null)
            return false;
        if(pushA.length != popA.length)
            return false;
        Stack<Integer> stack = new Stack<Integer>();
        int pushindex = 0, popindex = 0;
        while(pushindex < pushA.length && popindex < popA.length){
            stack.push(pushA[pushindex++]);
            while(!stack.empty()&&stack.peek()==popA[popindex]){
                stack.pop();
                popindex ++;
            }
        }
        if(stack.empty())
            return true;
        return false;
    }
}

最小的k个数

(1)利用Java中的优先队列,堆排序思想

/*
这里采用优先队列的思想(大顶堆)
维护一个大顶堆,每次加入元素后如果多余k个就删除最大的
所有加入一遍后就剩下了k个最小的。

PriorityQueue的用法
大顶堆 new PriorityQueue<Integer>((o1,o2)->(o2-o1));
小顶堆 new PriorityQueue< >();
自定义 Queue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
*/
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> ret = new ArrayList<>();
        if(input == null || input.length == 0|| k > input.length)
            return ret;
        Queue<Integer> priorityQueue = new PriorityQueue<Integer>((o1,o2)->(o2-o1));
        for(int i=0;i<input.length;i++){
            /*
            (一个重要的细节)
            每次都要先将当前元素加入队列,再判断是否大于k个,删除最大的
            (如果先删再加,就不能保证删除的这个元素是大于新加入的元素)
            */
            priorityQueue.add(input[i]);
            if(priorityQueue.size()>k)
                priorityQueue.poll();
        }
        while(priorityQueue.size()>0){
            Integer number = priorityQueue.poll();
            ret.add(number);
        } 
        return ret;
    }
}

(2)利用快速选择

/*
利用快速排序的思想
找到第k个位置,这个位置之前的所有元素小于他,之后的所有元素大于他
(因为本题给出的要求的是输出的这个k个最小的数顺序可以是任意的所以可用该方法)
*/
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> ret = new ArrayList<>();
        if(input == null||input.length<k||k<=0)
            return ret;
        int start = 0, end = input.length-1;
        int index = partition(input,start,end);
        //比较是一定要用k-1比较,用k比较虽然可以找到前k个最小,但会出现数组越界访问
        while(index!=k-1){
            if(index<k-1)
                start = index+1;
            if(index>k-1)
                end = index -1;
            index = partition(input,start,end);
        }
        for(int i = 0;i<k;i++){
            ret.add(input[i]);
        }
        return ret;
        
    }
    private int partition(int[] input, int start, int end){
        int number = input[end];
        while(start<end){
            while(start<end&&input[start]<=number)start++;
            if(start<end)
                input[end--] = input[start];
            while(start<end&&input[end]>number)end--;
            if(start<end)
                input[start++] = input[end]; 
        }
        //不要忘记最后要把number放回他应该的位置
        input[start] = number;
        return start;
    }
}

以上两个方法各有优劣

快排思想需要修改数组元素。

大顶堆思想不需要修改数组元素 ,但需要维护一个优先队列,适合海量数据(即n比较大,k比较小,不能够一次性将数据载入内存的)

数据流中的中位数

import java.util.*;
/*
从第0个元素开始
第偶数个放左边,第奇数个放右边
为了保证左边永远小于右边所有元素,右边永远大于左边所有元素
则当一个元素要加入左边时,其可能并不小于右边,故先加入右,再弹出右边最小加入左边
反之依然
*/
public class Solution {
    private Queue<Integer> left = new PriorityQueue<Integer>((o1,o2)->(o2-o1));
    private Queue<Integer> right = new PriorityQueue<Integer>();
    private int N = 0;
    public void Insert(Integer num) {
        if((N&1)==0){
            right.add(num);
            left.add(right.poll());
        }
        else{
            left.add(num);
            right.add(left.poll());
        }
        N++;
    }

    public Double GetMedian() {
        if(N == 0)
            return -1.0;
        //因为是实时的插入获取,故这里计算中位数时只能获取不能弹出
        //注意要求的返回值类型
        if((N&1) == 0)
            return Double.valueOf(left.peek()+right.peek())/2;
        else
            return Double.valueOf(left.peek());
    }


}

字符流中第一个不重复的字符

import java.util.*;
public class Solution {
    //Insert one char from stringstream
    /*
    不同字符对应不同的ASCII码共有128种
    用一个长为128的数组记录每个字符出现的次数
    用队列存储字符读取顺序
    */
    private int[] num = new int[128];
    private Queue<Character> queue= new LinkedList<>();
    public void Insert(char ch)
    {
         num[ch]++;
         queue.add(ch);
         //保证每次读入操作后,队列的头元素都是目前为止首个为重复元素
         while(!queue.isEmpty()&&num[queue.peek()]>1)
             queue.poll();
        
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        return (queue.isEmpty())?'#':queue.peek();
    }
}

滑动窗口的最大值

import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size) {
        //存储滑动窗口最大值
        ArrayList<Integer> arrayWindow = new ArrayList<>();
        //对当前滑动窗口内的元素进行堆排序
        PriorityQueue<Integer> max = new PriorityQueue<>((o1,o2)->o2-o1);
        if( size <= 0 || size > num.length )
            return arrayWindow;
        /*
        初始化arrayWindow和max堆
        */
        for(int i = 0; i < size; i++){
            max.add(num[i]);
        }
        arrayWindow.add(max.peek());
        /*
        模拟窗口滑动
        窗口每移动一次,先从堆中删除从窗口中滑出的元素,再将新滑入的元素加入max堆中;
        取max的peek获取最大值存入arraywindo
        */
        for(int i = size; i < num.length; i++){
            max.remove(num[i-size]);
            max.add(num[i]);
            arrayWindow.add(max.peek());
        }
        return arrayWindow;
    }
}

双指针

和为S的两个数字

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        ArrayList<Integer> list = new ArrayList<>();
        /*
        分别用两个值指向数组的头和尾
        每次将头尾元素相加查看结果是否为sum
        若<sum 头指针向后移动
        若>sum 尾指针像前移动
        */
        int head = 0 , tail = array.length-1;
        while(head < tail){
            if(array[head]+array[tail] == sum){
                list.add(array[head]);
                list.add(array[tail]);
                return list;
            }
            else if(array[head]+array[tail] 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值