剑指Offer相关内容(1-6)

本文精选《剑指Offer》中的经典题目,包括二维数组查找、字符串空格替换、链表逆序打印、二叉树重建、双栈实现队列及旋转数组最小值查找。深入解析算法原理,提供高效解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

剑指offer程序题设计相关的知识点(Java语言)

1.二维数组中的查找

  在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数

  注意
   1.对于一个二维数组array,row = array.length;col = array[0].length
   2.根据此二维数组的规律,即一行数组从小到大,一列数字也是从小到大,所以遍历从每一行的最后一个数字开始遍历,移动方向为向左向下

2.替换空格

  请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

  注意:
   1.注意StringBuffer和String的区别,需要将StringBuffer类别转换为String类别,StringBuffer str;String s = new String(str);
   2.可以直接用Sting类型数据的replace()方法直接替换目标字符(串)s.replace(' ", "%20")
   3.可以用一种更快更少内存的方法,Java中有一个indexOf()方法,用于锁定某字符的位置,str.indexOf(" "),可以先不用进行字符串转换,然后用StringBuffer的replace()方法替换字符,str.replace(begin,begin+1, "%20"),不包括end,然后接着begin的位置再往下寻找新的indexOf,str.indexOf(" ", begin)
   4.在字符串不经常发生变化的业务场景优先使用String(代码更清晰简洁)。如常量的声明,少量的字符串操作(拼接,删除等)。
   5.在单线程情况下,如有大量的字符串操作情况,应该使用StringBuilder来操作字符串。不能使用String"+"来拼接而是使用,避免产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)。如JSON的封装等。
   6.在多线程情况下,如有大量的字符串操作情况,应该使用StringBuffer。如HTTP参数解析和封装等。

3.从头到尾打印列表

  输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

  注意:
   1.首先要创建一个链表,包括值和指针。

    public class ListNode {
            int val;
            ListNode next = null;
    
            ListNode(int val) {
                this.val = val;
            }    
        }

   2.创建两个arraylist,用于存储正序和倒叙的链表。

    import java.util.ArrayList;
    public class Solution {
        public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
            ArrayList<Integer> list = new ArrayList<Integer>();
            ArrayList<Integer> result = new ArrayList<Integer>();
            ListNode temp = listNode;
            while(temp!=null){
                list.add(temp.val);
                temp = temp.next;
            }
            for(int i=list.size()-1;i>=0;i--){
                result.add(list.get(i));
            }
            return result;
        }
    }

   3.一个链表存储的内容是头节点的值以及next指针,根据这两个值客可以遍历整个链表。

4.重建二叉树

  输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

  注意:
   1.首先要定义一个二叉树。

      public class TreeNode {
          int val;
          TreeNode left;
          TreeNode right;
          TreeNode(int x) { val = x; }
      }

   2.定义一个整数,用于代表在前序中根节点的位置。

  private int pre_index = 0; //walk in pre,for find the value next root

   3.定义一个函数,用于实现在某个区间搜索特定值的索引。

       // find the root, the subtree brlong to [x,y]
        public int Find(int target, int x, int y, int shuzu[]){
            int i;
            for(i=x;i<=y;i++){
                if(shuzu[i]==target){
                    break;
                }
            }
            return i;
        }

   4.定义一个函数,用于利用前序和中序遍历结果,递归在左右子树中寻找父节点,依次输出(根节点 -左子树-右子树,构建二叉树的顺序)。

    public TreeNode LeftNodeAndRightNode(int x, int y, int shuzu[]){
        int index = Find(pre[this.pre_index++], x, y, shuzu);
        TreeNode temp = new TreeNode(shuzu[index]);
        if(index!=x){
            temp.left = LeftNodeAndRightNode(x, index - 1, shuzu);
        }
        if(index != y){
            temp.right = LeftNodeAndRightNode(index+1, y, shuzu);
        }
        return temp;
    }

   5.先通过前序遍历确认根节点,之后调用以上两个函数,在左右子树中递归遍历。

public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        TreeNode tn = new TreeNode(pre[0]);// the tree only have root
        this.pre = pre;
        int in_len = in.length;
        int rootNode_index = Find(pre[pre_index++], 0, in_len, in);
        if(rootNode_index!=0){
            tn.left = LeftNodeAndRightNode(0, rootNode_index-1,in);
        }
        if(rootNode_index!=in_len-1){
            tn.right = LeftNodeAndRightNode(rootNode_index+1, in_len-1, in);
        }
        return tn;
    }

5.用两个栈实现队列

  用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

  注意:
   1. 思路:由于队列是先进先出的,而栈是先进后出的,所以要用2个栈来实现队列的入队出队功能,队列的入队功能与栈的一样,出队时,先将第一个栈中的元素全部弹出,并倒入到第二个栈中,将第二个栈中栈顶元素弹出,并将stack2中剩下的元素倒回到stack1中,即实现一次出队。

import java.util.Stack;
public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>(); //主队列
    Stack<Integer> stack2 = new Stack<Integer>(); //辅助
    
    //入栈函数
    public void push(int node){
        stack1.push(node); // the push() of stack
        
    //出栈函数
    public int pop(){
        Integer re = null;
        if(!stack2.empty()){    //如果栈2不是空的,就把最上面的取出来(第一次操作肯定是空的)
            re = stack2.pop();  //除了第一次不执行,之后都执行这一句,取出栈顶元素
        }else{
            // 如果栈2是空的,就把栈1里的数一个个取出来,放到栈2里
            while(!stack1.empty()){
                re = stack1.pop();
                stack2.push(re);
            }
            if(!stack2.empty()){
                // 栈2里面有数之后,再次把里面的数取出来
                re = stack2.pop();
            }
        }
        return re;
    }
}

6.旋转数组的最小数字

  把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转*。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。*

  注意:
   1. 第一种是自己的方法,因为数字是非递减的,所以遍历旋转数组,依次将当前数字与后一个数字比较,如果后一个数字比当前数字小,则一定是前面的元素,如果遍历完还没有这种情况,一直是相等,那说明数组中的数字大小一致。这种方法的速度更快,更简单。

    import java.util.ArrayList;
    public class Solution {
        public int minNumberInRotateArray(int [] array) {
            /* 逐个比较法(快)
            int len = array.length;
            if(len==0){
                return 0;
            }
            for(int i=0;i<len-1;i++){
                if(array[i]<=array[i+1]){
                    if(i==len-2){   //!!!!!注意索引不要出界
                        return array[i];
                    }
                    continue;
                }else{
                    return array[i+1];
                }
            }
            return 0;
    }

   2. 第二种是二分法比较。这种方法的速度慢,复杂。

思路:二分查找(慢)
mid = low + (high - low)/2
需要考虑三种情况:
(1)array[mid] > array[high]:
出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。
low = mid + 1
(2)array[mid] == array[high]:
出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边
还是右边,这时只好一个一个试 ,
low = low - 1
high = high - 1
(3)array[mid] < array[high]:
出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左
边。因为右边必然都是递增的。
high = mid
注意这里有个坑:如果待查询的范围最后只剩两个数,那么mid 一定会指向下标靠前的数字
比如 array = [4,6]
array[low] = 4 ;array[mid] = 4 ; array[high] = 6 ;
如果high = mid - 1,就会产生错误, 因此high = mid
但情形(1)中low = mid + 1就不会错误

    import java.util.ArrayList;
    public class Solution {
        public int minNumberInRotateArray(int [] array) {
            int low = 0;
            int high = array.length-1;
            while(low<high){ 
                int mid = (low + high) / 2;
                if(array[mid]>array[high]){   // 如果中间值更大,说明最小值在中间的右边 移动low指针
                    low = mid + 1;
                }
                else if(array[mid]==array[high]){  // 如果两个值相等,难以判断 数组有重复数字 要一个一个试试
                    low = low - 1;
                    high = high - 1;
                }else{                            
                   // 如果中间值小,说明最小值在左边 移动high指针 但是如果只剩两个数字 mid返回的是low位置的 此时high=mid-1会报错 所以high=mid
                    high = mid;
                }
            }
            return array[low];   // 最后只会剩两个 最小值肯定在low位置
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值