剑指OFFER思路总结与代码分享——数组和字符串篇(Java实现)


注:顺序是先筛选分类再按LeeCode上的通过率排的,每题最后的总结代码都是在LeeCode上跑过的,应该没啥问题。但是思路中的代码都是直接在CSDN编辑器里徒手敲的,若有笔误还烦请告知,蟹蟹~

03 数组中的重复数字

在这里插入图片描述
第一反应是用HashMap做,反正啥玩意都HashMap一把梭,顺便说一个优雅的往HashMap里放key来计数的操作:

map.put(i, map.getOrDefault(i, 0) + 1);

getOrDefault()呢就是要是有你说的key就返回value,没有就返回你设置的默认值,我们这样设置在用HashMap计数的时候就超方便了。

class Solution {
    public int findRepeatNumber(int[] nums) {
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int num:nums){
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        for(int key:map.keySet()){
            if(map.get(key) > 1){
                return key;
            }
        }
        return -1;
    }
}

然后发现这玩意时间跑过了7%的人…这玩个锤子,继续想方法优化吧。看到题目里这个条件:所有数字的范围都在0到n-1之间,这说明了我们可以一个数对应一个下标,要是有冲突就说明这个数是重复的。

class Solution {
    public int findRepeatNumber(int[] nums) {
        for(int i = 0; i < nums.length; i++){
           if(nums[i] == i){	//若我这个数就在我这个坑,则继续
               continue;
           }else if(nums[i] == nums[nums[i]]){	//若我这个数与我想占得坑冲突了,那就是重复
               return nums[i];
           }else{	//我想占得坑里是别的数,就和他换一下,占好自己的坑
               int tmp = nums[i];
               nums[i] = nums[tmp];
               nums[tmp] = tmp;
           }
        }
        //找不到重复,返回-1
        return -1;
    }
}

速度变成了92%,这个优化还行。

53-I 在排序数组中查找数字

在这里插入图片描述
这种题基本上是要写二分查找的。二分查找这玩意是简单的思路,细节的魔鬼,恐怖的一匹,借着这题好好探究一下。
二分查找的等号在哪很关键,代表着我们要考虑在nums[mid] == target的时候怎么做,在这题里,我们在nums[mid] == target时,需要right = mid,即把right指针往前压,导致其二分的范围越来越往前压,找左边界。

class Solution {
    public int search(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        int count = 0;
        while(left < right){
            int mid = left + (right - left) / 2;
            if(nums[mid] >= target){
                right = mid;
            }
            if(nums[mid] < target){
                left = mid + 1;
            }
        }
        while(left < nums.length && nums[left++] == target){
            count++;
        }
        return count;
    }
}

再贴一个左右边界法来研究一下二分查找,此解答源自这里

public class Solution {

    public int search(int[] nums, int target) {
        int len = nums.length;
        if (len == 0) {
            return 0;
        }

        int firstPosition = findFirstPosition(nums, target);
        if (firstPosition == -1) {
            return 0;
        }

        int lastPosition = findLastPosition(nums, target);
        return lastPosition - firstPosition + 1;
    }

    private int findLastPosition(int[] nums, int target) {
        int len = nums.length;
        int left = 0;
        int right = len - 1;
        while (left < right) {

            // 注意,以下 nums[mid] < target 以及 nums[mid] == target 的情况可以合并
            // 边界是 left = mid ,取中间数的时候必须 + 1
            int mid = (left + right + 1) >>> 1;

            if (nums[mid] < target) {
                // mid 以及 mid 左边都不是,下一轮搜索区间在 [mid + 1, right]
                left = mid + 1;
            } else if (nums[mid] == target) {
                // mid 可能是,mid 左边一定不是,下一轮搜索区间在 [mid, right]
                left = mid;
            } else {
                // 此时 nums[mid] > target
                // mid 以及 mid 右边都不是,下一轮搜索区间在 [left, mid - 1]
                right = mid - 1;
            }
        }
        return left;
    }

    private int findFirstPosition(int[] nums, int target) {
        int len = nums.length;
        int left = 0;
        int right = len - 1;
        while (left < right) {
            int mid = (left + right) >>> 1;

            if (nums[mid] < target) {
                // mid 以及 mid 左边都不是,下一轮搜索区间在 [mid + 1, right]
                left = mid + 1;
            } else if (nums[mid] == target) {
                // mid 可能是,mid 右边一定不是,下一轮搜索区间在 [left, mid]
                right = mid;
            } else {
                // 此时 nums[mid] > target
                // mid 以及 mid 右边都不是,下一轮搜索区间在 [left, mid - 1]
                right = mid - 1;
            }
        }

        if (nums[left] == target) {
            return left;
        }
        return -1;
    }
}

总结一下,在相等的情况下,你left = mid就是把下一轮的空间压在右边了,找的是右边界;你right = mid就是把下一轮空间压在左边了,找的是左边界。感觉我讲的不是很清楚,你还可以多找些专业的分析文章看看,我这里只是提醒你注意这块罢了。

53-II 0到n-1中缺失的数字

在这里插入图片描述
我的思路是按这个序列缺失了一个数走的,所有这个序列本来的长度应该是0~length,不用减一,这样最后出的时候发现0~n-1有序,那就直接指到n上去。

class Solution {
    public int missingNumber(int[] nums) {
        //缺失了一个数,所以本来应该有0-nums.length个数
        int left = 0,right = nums.length;
        while(left < right){
            int mid = left + (right - left) / 2;
            //如果出现错位了,那么首个错位的数字肯定在它左边,我们要压缩右边界
            if(nums[mid] != mid){
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        return left;
    }
}

29 顺时针打印矩阵

在这里插入图片描述
这题也是个细节题,一看就会一写就废,先列个表:
在这里插入图片描述
主要解释一下判停那块,就是我做完从左向右了之后,下一步需要做从上到下了,在做从上到下之前我需要把t加一,看一下它有没有超过b,要是超过了就说明可以结束了,其余同理。这题主要玩的就是++ii++++i是不多BB先加上1,然后再干活;i++是先干活,干完之后等这句话结束了再加1。

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if(matrix.length == 0){
            return new int[0];
        }
        int t = 0, b = matrix.length - 1, l = 0, r = matrix[0].length - 1;
        int index = 0;
        int[] res = new int[(b+1)*(r+1)];
        while(true){
            for(int i = l; i <= r; i++){
            	//我先把res[index] =matrix[t][i]给干完了,等这句话结束的分号时我再加一
                res[index++] = matrix[t][i];
            }
            //我不管你是个啥我先加上1,然后再跟你b去比一比
            if(++t > b) break;
            for(int i = t; i <= b; i++){
                res[index++] = matrix[i][r];
            }
            if(--r < l) break;
            for(int i = r; i >= l; i--){
                res[index++] = matrix[b][i];
            }
            if(--b < t) break;
            for(int i = b; i >= t; i--){
                res[index++] = matrix[i][l];
            }
            if(++l > r) break;
        }
        return res;
    }
}

04 二维数组中的查找

在这里插入图片描述
兄弟萌这题不要从matrix[0][0]开始走啊,你看matrix[0][0]往右是比他大的往下还是比他大的,你说你是走右边还是走下边?他就是个弟中弟。兄弟萌matrix[0][n]了解一下啊,你看这个15,往左走11比他小,往下走19比他大,绝对不会选择恐惧症,完美啊。对了,二维数组做之前先把行列想清楚,不然很容易写反,蛋疼。

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if(matrix.length == 0 || matrix == null){
            return false;
        }
        //咱们从matrix[0][n]开始走
        int n = matrix[0].length - 1, m = 0;
        while(n >= 0 && m <  matrix.length){
        	
            if(matrix[m][n] > target){
            	//刚说了,目标值比他小,那就往左找
                n--;
            }else if(matrix[m][n] < target){
            	//目标值比他大,就往下找
                m++;
            }else{
            	//这可不就找到了
                return true;
            }
        }
        return false;
    }
}

下面开始是三题字符串了,三题的通过率断崖式下跌,我开始慌了
在这里插入图片描述

58-II 左旋转字符串

在这里插入图片描述

class Solution {
    public String reverseLeftWords(String s, int n) {
        return s.substring(n) + s.substring(0, n);
    }
}

双百,小朋友你是否有很多问号?先唠一下substring是左闭右开,我在树的07 重建二叉树那题里唠过,那题用到了Arrays.copyOfRange还记得不?反正写的时候实在不行你找个例子写在纸上,然后念叨左闭右开左闭右开截就完事了。我看了下大佬们的其他做法,好像都差不多,那就直接下一题吧。

58-I 翻转单词顺序

在这里插入图片描述
这题面经里好像也经常见哎,我用了正则表达式来解决多个空格的问题,其他的好像没啥好说的,记得几个常用的String方法吧:

//帮你把最前面和最后面的空格都去掉的无情杀手
str.trim();
//按照你给的内容把字符串拦腰截断的无情杀手二号
str.split("balabala");
//我们的老朋友,左闭右开的无情杀手三号
str.substring(1,2);
//可以变长变短的String的好基友sb同学
StringBuilder sb = new StringBuilder();
//我们想到sb同学就会想到他的特有技能:把东西加在他屁股后面
sb.append("ass");
//sb不做人啦,他要变回String不然八成没法return
sb.toString();

对了,所有对字符串的操作必须赋值回去,不然是不生效的,因为字符串是不可改变的。

//可以
s = s.trim();
//不可以
s.trim();

正则表达式可以看这里,下面上代码:

class Solution {
    public String reverseWords(String s) {
        s = s.trim();
        //" +"可以匹配一个或多个空格
        String[] strs = s.split(" +");
        StringBuilder sb = new StringBuilder();
        for(int i = strs.length - 1; i >= 0; i--){
            sb.append(strs[i]);
            sb.append(" ");
        }
        //这里注意我们最后还加了个空格,所以再trim一下把最后那个空格给它去掉
        return sb.toString().trim();
    }
}

67 把字符串转化为整数

在这里插入图片描述
后端想打死测试工程师系列。我中了一个" "这个用例,想来我一上来就判空了,应该先trim()再判空的,卑微。其他还好,思路对了就不难,倒杯咖啡慢慢做呗。先说几个这题的知识点吧:
首先是char转int记得减个0,因为大家都是ASCII码做运算的:

int i = ch[index] - '0';

其次是怎么去掉数组的第一位,我没出息的用了copyOfRange(),还行吧,可能有好方法,评论区call我哦~
最后是个小细节,在while()条件里的:

while(index < ch.length && Character.isDigit(ch[index]))

你把这两个先后顺序一掉,就会报空指针了,因为&&短路与,它判断第一个条件不满足就滚蛋了,不会判第二个了,你index超了直接滚蛋,第二个ch[index]就不管了,超不超边界无所谓。你要反过来的话就先判ch[index],你这时候超了就空指针了。如果你想两个都判可以用&就完事了。||同理。
我直接把思路写在注释里了,先上个没注释的版本,再上个注释版,不然太乱了:

class Solution {
    public int strToInt(String str) {
        str = str.trim();
        if(str.length() == 0){
            return 0;
        }
        boolean isMinus = false;
        char[] ch = str.toCharArray();
        if(ch[0] == '+' || ch[0] == '-' || Character.isDigit(ch[0])){
            if(ch[0] == '+' || ch[0] == '-'){
                if(ch[0] == '-'){
                    isMinus = true;
                }
                ch = Arrays.copyOfRange(ch,1,ch.length);
            }
            int index = 0;
            long res = 0;
            while(index < ch.length && Character.isDigit(ch[index])){
                res *= 10;
                res += ch[index] - '0';
                if(res > Integer.MAX_VALUE){
                    return isMinus ? Integer.MIN_VALUE : Integer.MAX_VALUE;
                }
                index++;
            }
            return isMinus ? -(int)res : (int)res;
        }
        return 0;
    }
}

注释版:

class Solution {
    public int strToInt(String str) {
    	//先去空格再判空,血的教训
        str = str.trim();
        if(str.length() == 0){
            return 0;
        }
        //然后我想啊,下面要判断首位了,首位合格的无非就'+'或'-'或数字三种情况,其他的一概滚蛋
        //'+''-'肯定是要把它去掉的,这样三种情况就统一了
        //当然了,'-abc'这种有可能出现,不过只看首位它是没毛病的,让它进来,反正后面很容易解决
        //既然要去掉正负号,那肯定要出个boolean记一下是不是负数
        boolean isMinus = false;
        char[] ch = str.toCharArray();
        //首位是不是正负号或者数字啊
        if(ch[0] == '+' || ch[0] == '-' || Character.isDigit(ch[0])){
        	//是不是正负号啊
            if(ch[0] == '+' || ch[0] == '-'){
            	//是不是负号啊
                if(ch[0] == '-'){
                    isMinus = true;
                }
                //删除首位
                ch = Arrays.copyOfRange(ch,1,ch.length);
            }
            //首位搞定了就看后面是不是数字了,直到不是数字的地方或者倒底结束
            int index = 0;
            //结果可能超int范围,拿个long接一下
            //'-abc'这种情况返回的也是0,舒服,一箭双雕
            long res = 0;
            //短路与助您远离空指针喔,铁汁们,先后顺序关注一下
            while(index < ch.length && Character.isDigit(ch[index])){
                //一位一位往上算
                res *= 10;
                res += ch[index] - '0';
                //及时止损,一看到res超int范围立马return
                //你要是想着最后一起算,那肯定会有超long范围的测试用例等着你,你就哭去吧
                if(res > Integer.MAX_VALUE){
                	//正负号看是正数负数,返回最大值
                    return isMinus ? Integer.MIN_VALUE : Integer.MAX_VALUE;
                }
                //别忘了往后走一位
                index++;
            }
            //long转int就是这么朴实无华
            return isMinus ? -(int)res : (int)res;
        }
        //兄弟首位都不对想啥呢,回去吧您
        return 0;
    }
}

到此为止数组和字符串的部分就结束啦~求个关注啵啵啵(●´З`●)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值