算法笔记——每日一题(完结)

算法笔记 From Now To My Death


此篇以完结:为了抓住面试重点:转向剑指Offer每日一题系列。Click me forward to new article

前言

以前当兵的时候,每次搞30公里强行军都很累。于是我会在50斤的背囊上写着:行百里者半九十——>靠着这句话,即使腿抽筋着,我都能坚持自己完成下来。
现在,我同样用这句话来激励自己,天下没有难学的技术,只有半途而废的人。不要求我一定能成为技术大牛(毕竟这需要一定的天赋和机遇)
旦求无愧于自己。平凡而不平庸即可。

比昨天的自己更好一点,比明天的自己更差一点

在这里插入图片描述

初级算法

1、两数之和

给定一个数组nums,和一个整数目标值target。从数组中找出两个元素,他们的和 = target。
返回对应两个元素的数组下标。

public int[] twoSum(int[] nums, int target){
   
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0; i < nums.length(); i ++){
   
// 利用 【Map集合的containsKey API】
//  逆向思维: target = key + nums[i] 
//       ===> map.key = target - nums[i]时 返回结果!
if(map.containsKey(target - nums[i])){
   
return new int[]{
   map.get(target - nums[i]), i};
}
}
map.put(nums[i],i);
}
return null;

如果用双层for循环固然简单,但是时间复杂度为O(n^2)
解题思路:
在这里插入图片描述


7、整数反转

给你一个32位的有符号整数x,返回将x中的数字反转后端结果。
如果反转后的整数超过32位的有符号整数的范围[-2^31, 2^31 - 1]——> 返回 0

public int reverse(int x) {
   
32位整数范围是:-2147483648 ~ 2147483647
为快速判断:只要将反转后的值,与最大值的最后两个数:十位 / 个位 进行比较即可
1、当反转后的值 大于十位之前的值时:无论它的各位数是几,都是越界的:214748365*2147483647
2、当反转后的值 等于十位之前的值时:判断其个位 是否>最大值的个位:214748364*2147483647
        int res = 0;  // 初始 0
        while(x!=0) {
   
            每次摘下当前 x 的个位
            int tmp = x%10;
            当摘下->放入 执行到2147483649位时,即可进行判断
            (如果全部反转完再判断,则会抛出异常)
            因此,反转到最后2个(十位)时,如果大于了最大值的十位,则没必要比较个位了、
            如果反转到十位时,发现和最大值的十位及其之前的数都相等。则需要再反转一次:比较个位
            //判断是否 大于 最大32位整数
            if (res>214748364 || (res==214748364 && tmp>7)) {
   
                return 0;
            }
            //判断是否 小于 最小32位整数
            if (res<-214748364 || (res==-214748364 && tmp<-8)) {
   
                return 0;
            }
            // res:当前这一步while反转后的值
            res = res*10 + tmp; 将当前 x 的个位,放入反转后的值的个位中
            //  由于 原值 x 的末尾数字已经被取走:放入res中了。
            // 因此 原值x 就少扣除末尾的那一位。
            x /= 10;
        }
        return res;
    }

9、回文数

给你一个整数x,如果x是一个回文整数,返回true。否则返回false。
eg:123不是回文。121是回文
直接StringBuilder.reverse => 但是这样要额外创建对象、并且反转整个字符串来比较
实际上:只要反转x的前半段,然后与后半段比较即可、如11222211; 反转1122—>2211 == 后半段!

    private static boolean test(int x){
   
        if (x <= 0 || ( x % 10 == 0 && x != 0)){
   
            return false;
        }
        int revertedNum = 0;
        // 通过 % 和 /的方式,达到string字符串remove的效果
        // eg: 1122332211  ==> 每次从末尾摘除一位,赋给revertedNum后,x就要扣除一位
        // 最后 x = 11223 == revertedNum = 11223 退出循环!
        while (x > revertedNum){
   
            // 反转后的数 = 上次反转的数 * 10 + 本次 x 的末尾摘下的个位
            revertedNum = revertedNum * 10 + x % 10;
            // 本次x被摘下了个位,于是就x剩下 x / 10;
            // 这样只需要摘除x的一半长度时,即可判断是否为回文数
            x = x / 10;
        }
        // 如果x是奇数:那么退出循环的结果会是 x=1122 revertedNum=11223
        // 所以x == revertedNum / 10 时也为true;
        return x == revertedNum || x == revertedNum / 10;
    }

14、最长公共前缀

寻找一个字符串数组中的最长公共前缀,不存在则返回""

    private static String test(String[] arr){
   
        if (arr.length == 0){
   
            return "";
        }else if (arr.length == 1){
   
            return arr[0];
        }
        String commonString = "";
        String first = arr[0];
        int flag = 0;
        for (int i = 0; i < first.length(); i++) {
   
            for (int j = 1; j < arr.length; j++) {
   
                if (!arr[j].startsWith(first.substring(0, i))){
   
                    flag = 1;
                    break;
                }
            }
            if (flag == 1){
   
                break;
            }
            commonString = first.substring(0, i);
        }
        return commonString;
    }

27、移除元素【拷贝复制】

给你一个整数数组、和一个目标值val。移除该数组中所有值等于val的元素。返回移除后的长度。
要求:原地修改数组、不使用额外的空间。

 public static void main(String[] args) {
   
        int[] nums = {
   1,2,3,4,5,6};
        System.out.println(removeElement(nums,5));
    }

    private static int removeElement(int[] nums, int val){
   
        int result = 0;
        for (int i = 0; i < nums.length; i++) {
   
       //在原数组上:发现与val相同的元素,则跳过
       // 与val不同的元素,则放入数组前面,保存下来。并且长度++
            if (nums[i] != val){
   
                nums[result] = nums[i];
                result++;
            }
        }
        return result;
    }

28、实现strStr()【双指针】

给你两个字符串haystack和needle,请你再haystack字符串中找出needle字符串出现的第一个位置
如果不存在,则返回 -1; 当needle字符串为空时,应该返回0;
这与C语言定义的strStr()函数以及Java定义的indexOf()函数相当
双指针在数组遍历中非常非常地常见

    private static int indexOf(String haystack, String needle){
   
        int result = 0;
        if (haystack.equals("") || needle.length() > haystack.length()){
   
            return -1;
        }
        if (needle.equals("")){
   
            return 0;
        }
        int left = 0, right = needle.length();
        while (right < haystack.length()){
   
            String substring = haystack.substring(left, right);
            if (substring.equals(needle)){
   
                return left;
            }
            left++;
            right++;
        }
        return result;
    }

35、搜索插入位置

很简单的一题、没啥可说的。
给你一个无重复元素的升序数组、给你一个target、找出target的插入位置。如果已有target则返回索引

    private static int searchInsertPosition(int[] nums, int target){
   
        if (target < nums[0]){
   
            return 0;
        }
        if (target > nums[nums.length - 1]){
   
            return nums.length;
        }
        int left = 0, right = nums.length - 1;
        int mid = 0;
        while (left < right){
   
            mid = (left + right) / 2;
            if (nums[mid] == target){
   
                return mid;
            }
            if (nums[mid] < target){
   
                left = mid + 1;
            }else {
   
                right = mid - 1;
            }
        }
        return mid;
    }

==============================================================================

中级算法

2、两数相加【预先指针】

给你两个【非空】链表,表示两个非负整数。他们的每位数字都是按照【逆序】的方式存储的,
并且每个节点只能存储【一位】数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字0之外,这两个数都不会以0开头。
示例:l1 = [2, 4, 3] 、 l2 = [5, 6, 4]、 342 + 465 = 708 、、 return [7, 0, 8]

先看看我的解题思路:代码有点复杂
就是用简单的:每一位与每一位相加,>0则往上一位进1。我这里没有给短的List用0补全。
而LetCode大佬的解法是:把短的这个List用0补全——>构造出 l1长度 == l2,然后进行进位运算

	public ListNode addTwoNumbers(ListNode l1,ListNode l2){
   
	// 用于存储最后结果的list链表
        LinkedList<Integer> list = new LinkedList<>();
        //存储每次要进位的数
        int z = 0;
        //  如果传进来的l1 > l2 则交换一下位置
        if (l1.size() > l2.size()){
   
            return addTwoNumbers(l2,l1);
        }
        Iterator i1 = l1.iterator();
        Iterator i2 = l2.iterator();
        //  小的链表驱动大的表
        while (i1.hasNext()){
   
        // 取出l1的尾元素
            int last1 = l1.removeLast();
            // 取出l2的尾元素
            int last2 = l2.removeLast();
            //  计算两之和
            int sum = last1 + last2 + z;
            //  求出余数
            int y = sum % 10;
            //   求出进位的数  18则进位1  即 z = 1
            z = sum / 10;
            //  余数即可放入return的链表中了
            list.add(y);
        }
        //  当小的链表都取完了之后,直接取大的链表剩余的部分即可。 
        for (int i = 0; i < l2.size(); i++) {
   
            int last = l2.removeLast();
             // 注意也要加上之前余留的进位数 z
            int sum = last + z;
            int y = sum % 10;
            z = sum / 10;
            //  把余数加到return的list中
            list.add(y);
        }
        //  最后,如果进位!=0,则说明还没加完,把最后这个进位加到末尾即可。
        if (y != 0){
   
            list.add(z);
        }
        return list;
}

3、无重复字符的最长子串【滑动窗口】

给定一个字符串s,请你找出其中不含有重复字符的【最长子串】的长度
例如:输入s = “pwwkew” 输出3

//  博主的渣渣解题方法: 时间复杂度为O(m)
    public int getMaxLengthOfString(String s){
   
        String result = "";
        int max = 1;
        for (int i = 0; i < s.length(); i++) {
   
            String c = String.valueOf(s.charAt(i));
            System.out.println("char[" + i + "] -->" + c);
            System.out.println("result --pre-->" + result);
            // 判断目前筛选出的无重复字符的字符串result中,是否含有将要比对的这个字符 c
            if (result.contains(c)){
   
                System.out.println("此次result为" + result + ",发现重复字符:" + c);
                max = Math.max(max,result.length());
                System.out.println("目前筛选出的不重复字符串result的最大长度为:" + max);
                // 重置之前筛选出的result为当前发现的这个重复字符:继续往后筛选比对
                result = c;
                System.out.println("重置result! 重置后的result为:" + result);
            }else {
   
                System.out.println("当前result中不包含"+ c +",将" + c + "加入result中...");
                result = result + c;
                System.out.println("result --post-->" + result);
            }
            System.out.println("------------------------------>");
        }
        System.out.println("筛选完毕,返回结果------->");
        return max;
    }

LetCode大佬的【滑动窗口】算法
窗口:内含无重复字符的最长子串。每次找到重复字符,指针滑动到重复字符处!
坚持寻找无重复字符找了好久都没重复!突然发现了一个重复的!前功尽弃!在这里重新开始

    private static int getMaxLengthOfString(String s){
   
            int n = s.length(), ans = 0;
            //  key为字符 value为下标+1
            // map中存着目前已经扫描到的字符及其下标
            Map<Character, Integer> map = new HashMap<>();
            for (int end = 0, start = 0; end < n; end++) {
   
                char alpha = s.charAt(end);
                if (map.containsKey(alpha)) {
   
                // 如果map中已经包含的这个字符:即发现了重复字符
                //  则将起始指针移至重复字符的下标处
                    start = Math.max(map.get(alpha), start);
                }
                // 不包含则将本次的长度置为新的最大长度ans
                ans = Math.max(ans, end - start + 1);
                // 并将本次扫描过的字符放到map中, 以便下次contains比较
                map.put(s.charAt(end), end + 1);
            }
            return ans;
    }

5、最长回文子串【动态规划】

给你一个字符串s,找到s中的最长回文子串:即字符串关于中心对称(左=右)
如:s=“babad” return “bab”; s=“cbbd” return “bb”;

解法:动态规划!为减少重复计算:
每次都需要对内层字符串是否为回文串进行重复判断
将内层字符串是否为回文串缓存起来!这样就不用重复判断了
用一个boolean dp[l][r] (类似Redis,缓存着上一次的回文串) 表示字符串从i -> j是否为回文子串。
要判断i->j为回文子串,dp[l][r]=true===>即要判断它的前一位是否为回文子串dp[l-1][r-1]=true

    public String longestPalindrome(String s) {
   
        // 如果s的长度为1、那回文串就是她本身
        if (s == null || s.length() < 2) {
   
            return s;
        }
        int strLen = s.length();
        int maxStart = 0;  //最长回文串的起点
        int maxEnd = 0;    //最长回文串的终点
        int maxLen = 1;  //最长回文串的长度

        // 类似redis:缓存着上一轮的最大回文子串
        boolean[][] dp = new boolean[strLen][strLen];
        // r指针从1开始 -> 末尾
        for (int r = 1; r < strLen; r++) {
   
        // l指针从0开始 -> r   ==> 这样循环下来就能从左到右把所有可能性都遍历一次
            for (int l = 0; l < r; l++) {
   
                // 如果本次l=r,那么就要看看他们的前一位是否也为回文子串
    // 1.如果r-l<=2即长度为3的时候,那就不用判断dp=true。直接为true!eg:bab
                if (s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1])) {
   
                // 判断成功!将本次true记录一下
                    dp[l][r] = true;
        // 并且如果本次长度合法、则记录头指针和尾指针、用于返回最后结果
                    if (r - l + 1 > maxLen) {
   
                        maxLen = r - l + 1;
                        maxStart = l;
                        maxEnd = r;
                    }
                }

            }

        }
        return s.substring(maxStart, maxEnd + 1);
    }

6、Z字形变换

将一个给定字符串s根据给定的行数numRows,从上往下、从左往右进行Z字排序
eg:输入PAYPALISHIRING,numRows=3时。排列为:在这里插入图片描述
之后:将该Z字形排列从左往右逐行读取,产生新的字符串:PAHNAPLSIIGYIR

解答:
何时Z字形字符的方向开始变化!
1、每次在行数 = 0 和 numRows - 1 (即两端处方向发生变换)
2、rowNow = 0 时下一个元素所在行号:为本次行号 + 1,为numRows - 1时:回头:为本次行号 - 1
3、定义一个boolean的flag来标志下一行行号是该 + 1 还是 - 1 ?
用什么数据结构来存储每一行读出的字符?并方便最后结果的拼接?
4、定义一个String类型数组、长度为numRows、数组下标0对应第0行读出的字符、下标1对应第一行
在这里插入图片描述

    private static String convert(String s, int numRows){
   
        if (numRows == 1){
   
            return s;
        }
        // 这里按道理是直接取(行数)len = numRows,但要考虑特殊情况。
        // 如:s的长度比numRows还小时,直接输出s即可
        int len = Math.min(s.length(), numRows);
        // 声明一个字符串数组:下标为 0 1 2的分别存储第0 1 2行的字符
        // 最后再将这个数组中的字符串依次拼接即可
        String rows[] = new String[len];
        for (int row = 0; row < len; row++) {
   
            // 先通过for循环把需要的几行数组元素初始化为空串
            rows[row] = "";
        }
        // down为false时,为向右上走(i-1)。true为向下走(i+1)
        boolean down = false;
        // 定义当前所在行
        int rowNow = 0;
        // 开始顺序遍历s字符串:
        for (int i = 0; i < s.length(); i++) {
   
            // 第0个放第0行中,第1个放第1行...遇到方向变化则回头
            rows[rowNow] += s.charAt(i);
            // 在当前行数为0;或为numRows - 1:即在两端口时,需要变换方向
            if (rowNow == 0 || rowNow == numRows -1){
   
                down = ! down;
            }
            // 下次循环的行数rowNow是根据转向标志flag来判断是+1还是-1;
            rowNow += down ? 1 : -1;
        }
        // 至此,String数组中每个元素都存着对应下标行读取出的字符
        //  因此把他们从0->len拼接起来即可
        String result = "";
        for (int i = 0; i < len; i++) {
   
            System.out.println("--第" + i +
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Binary H.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值