剑指offer题解

算法是程序员能力的重要体现,学习和工作中要注意锻炼算法能力。

计划分专题完成该本书的题目

一、数组类型

1、数组中的重复数字

题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

分析:数组中数字的值是有限的,又是一个找重复数字的问题,很容易想到哈希法。

用原始数组的值对应新数组的下标,若原始数组中出现重复的数字,那么新数组该下标处对应的元素值为2。

代码

a、for循环第一次扫描出,统计元素出现的次数

b、在扫描一次hash数组,找到一个出现次数大于1的

public class Solution {
    
   public boolean duplicate(int numbers[],int length,int [] duplication) {
       
       if(numbers == null) return false;
       //判断数组是否合法(即数字范围在0到n-1)
       for(int i =0; i < numbers.length ; i++){
           if(numbers[i]<0 || numbers[i]> length -1){
               return false;
           }
       }
       //用hash计数
       int[] hash = new int[length];
       for(int i = 0 ; i < length; i ++){
           hash[numbers[i]] ++;
       }
       for(int i = 0 ; i < length; i ++){
           if(hash[i] > 1){
               duplication[0]=i;
               return true;
           }
       }
       return false;
   }
}

2、二维数组中的查找

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

分析:二维数组中的查找问题,且是特殊的二维数组,所以去找规律。

上图是查找的过程,我们发现如下规律:首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数字,剔除这个数字所在的列;如果该数字小于要查找的数字,剔除这个数字所在的行。也就是说如果要查找的数字不在数组的右上角,则每一次都在数组的查找范围中剔除一行或者一列,这样每一步都可以缩小查找的范围,直到找到要查找的数字,或者查找范围为空。

代码 :

a、拿到右上角的下标

b、target与右上角元素比较,target大,则剔除当前行;target小,则剔除当前列

public class Solution {
    public boolean Find(int target, int [][] array) {
       if(array == null || array.length == 0 || array[0].length == 0 ){
            return false;
        } 
        //初始位置(右上角)
        int hang = 0;
        int lie = array[0].length -1;
        //二维数组的行数
        int rows = array.length - 1;
        while(hang <= rows && lie >= 0){
            if(target == array[hang][lie]){
                return true;
            }
             else if(target > array[hang][lie]){
                hang ++;
            }
            else{
                lie --;
            } 
        }
        return false;
    }
}

 

二、字符串类型

很多时候字符串和数字是可以相互转换的,它们的解题思路类似。

1、替换空格

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

分析一:看到字符串替换,会一下想到String的各种方法,好像就有一个替换字符串的方法。

代码一

注意入参是StringBuffer类型的,要转为String类型操作。

public class Solution {
    public  String replaceSpace(StringBuffer str) {
/*利用String API的解法*/
        //判断
        if(str == null) return null;
        if( str.length() == 0) return "";
        
        String string = str.toString();
        return string.replaceAll(" ","%20");
    }
}      

分析二:字符串问题可以转为字符数组解决,替换的字符串的本质是对字符数组的操作。

代码二 :

a、统计空格字符的个数

b、创建新数组,大小为原来数组长度+2*空格字符数

c、从后往将原字符数组的元素填充到新数组中

public class Solution {
    public  String replaceSpace(StringBuffer str) {
        String string = str.toString();
        if(string.equals("")) return string;
        
        char[] charStr = string.toCharArray();
        int i = 0;
        int lengthSpace = 0;
        //统计空格字符个数
        while(i < charStr.length){
            if(charStr[i]== ' '){
                lengthSpace ++;
            }
            i ++;
        }
        int newCharStrLength = charStr.length + lengthSpace * 2 ;
        char[] newCharStr = new char[newCharStrLength];
        //从后往前遍历原来的数组
        int oldIndex = charStr.length - 1;
        int newIndex = newCharStrLength -1;
        while(oldIndex >= 0){
            if(charStr[oldIndex] != ' '){
                newCharStr[newIndex --] = charStr[oldIndex --];
            }else{
                newCharStr[newIndex -- ] = '0';
                newCharStr[newIndex -- ] = '2';
                newCharStr[newIndex -- ] = '%';
                oldIndex --;
            }
        }
        return new String(newCharStr);
    }
}

2、翻转单词顺序

题目:

  输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。

分析:

  字符串类的问题,转为数组问题,每个单词作为一个字符串,在从后往前拼接字符串。

代码:

class Solution {
    public String reverseWords(String s) {
        //转为String数组
        String[] strArr = s.trim().split(" ");
        StringBuilder sb = new StringBuilder();
        for(int i = strArr.length-1; i >= 0 ; i --){
            if(!strArr[i].equals("")){//特别注意,排除字符串
                sb.append(strArr[i]+" ");
            }
        }
        return sb.toString().trim();
    }
}

3、表示数值的字符串

题目:

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。

分析:情况很多,典型的分情况讨论,不漏的列出所有情况。

代码:

三、链表类型

1、链表中倒数第k个节点 

题目:

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.

分析:

  如果可以从尾往头遍历,很容易。但是题目要求只能从前往后遍历,可以用两个指针。

1、快指针在head+k处,慢指针在head处,两个指针间的距离维持为K(循环不变量)。

2、两个指针同时往后遍历,当快指针指向null时,慢指针依旧在快指针的前k个结点处(即链表倒数第K个结点)

代码: 

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode fastHead = head;
        ListNode newHead = head;
        while(k > 0){
            //fastHead在newHead前面K个节点存储,他俩的相对位置作为循环不变量
            fastHead =  fastHead.next;
            k--;
        }
        //当fastHead指向null时,newHead正好是倒数第k个结点。
        while(fastHead != null){
            fastHead = fastHead.next;
            newHead = newHead.next;
        }
        return newHead;
    }
}

2、反转链表   

题目:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

分析:

  可以用递归和非递归实现

递归:假设2后面的结点都反转好了(即head.next为头结点的链表反转好了),再只需将2指向1(head.next.next = head),1指向null(head.next = null)

非递归实现:假设1、2、3都已经反转好了,下面开始反转4。(3是新的头结点newHead,4是正在处理的结点curHead)接下来将两个结点向后推进(缩小问题规模),维持处理好的结点和将要处理的结点。

代码:

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*/
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode){
        //为了训练链表,先反转,在输出到ArrayList中
        ListNode reversedNode = reverseLinkedList(listNode);
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        while(reversedNode != null){
            arrayList.add(reversedNode.val);
            reversedNode = reversedNode.next;
        }
        return arrayList;
    }
   
    /*实现链表反转*/
/*    private ListNode reverseLinkedList(ListNode head){
        if(head == null|| head.next == null){
            return head;
        }
        ListNode newHead = reverseLinkedList(head.next);
        //原链表的第二个结点执行第一个结点,第一个结点指向null
        head.next.next = head;
        head.next = null;
        return newHead;
    }
*/  //用循环实现
    private ListNode reverseLinkedList(ListNode head){
        //newHead是新的链表头,开始时链表无结点为null
        ListNode newHead = null;
        //当前正在处理的结点
        ListNode curHead = head;
        while(curHead != null){
            //假设newHead前的结点都已经反转了包括newHead(比如4之前都反转好了,现在要反转4)
            //步骤:1、将5保存下来;2、4指向3;3、newHead和curHead都往后移一位
            ListNode next = curHead.next;
            curHead.next = newHead;
            newHead = curHead;
            curHead = next;
        }
        return newHead;
    }
}

课后题:

https://www.nowcoder.com/practice/0cff324157a24a7a8de3da7934458e34

https://www.nowcoder.com/practice/a632ec91a4524773b8af8694a51109e7

3、删除链表的节点  

题目:给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

示例 1:

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

分析:

  头结点可能就是值为val的结点,需要重新选出头结点。遍历链表维持[head,previous]这个区间,表示到该区间是处理好的不包含val值的结点,下次重previous.next这个节点开始判断。

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        
        //如果开头就是val值结点,那么头结点往后移.
        //如果全是val值结点,返回null
        while(head.val == val){
            head = head.next;
        }
        if(head == null){
            return null;
        }
        ListNode previous = head; //初始值未定
        //区间[head,previous]是已经处理好的,不包含val的,那么previous不为null
        while(previous.next != null){
            if(previous.next.val == val){
                previous.next = previous.next.next;
            }else{
                //没找到val,previous往前移
                previous = previous.next;
            }
        }
        return head;
    }
}

四、树

二叉树的遍历,重建二叉树

五、栈和队列

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值