LeetCode 面试热题一

开篇说明

此系列文章是关于LeetCode 精选 TOP 面试题 的题解文章,共136个题(不包括会员题),每篇 10 题。共十四篇文章。
LeetCode 精选 TOP 面试题链接

1.两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
解题思路:遍历数组,并查找,该值之前的数组有没有与之对应的值之和为 目标值

class Solution {
   public int[] twoSum(int[] nums, int target){
        HashMap<Integer,Integer> map = new HashMap<>(nums.length-1);
        map.put(nums[0],0);
        for (int i = 1;i<nums.length;i++) {
            if(map.containsKey(target - nums[i])){
                return new int[]{map.get(target - nums[i]),i};
            }
            map.put(nums[i],i);
        }
        return null;
    }
}

2.两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
请添加图片描述
解题思路:使用一个变量记录进位标志,在做加法的时候,加上这个进位标志。

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
         ListNode t = new ListNode(0);
         ListNode result = t;
         int c = 0;
         int sum;
         while(l1!=null || l2!=null){
            result.next = new ListNode(0);
            result = result.next;
            sum = (l1 == null?0:l1.val) + (l2==null?0:l2.val) + c;
            c = sum / 10;
            result.val = sum % 10;
            l1 = l1!=null?l1.next:null;
            l2 = l2!=null?l2.next:null;
         }
         if(c == 1){
            result.next = new ListNode(1);
         }
         return t.next;
    }
}

3.无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
解题思路:遍历字符串,记录字符出现的最后位置,如果前面已经出现,则回退到上一次出现的位置+1的位置,同时记录子串的大小。等待字符串遍历完成之后,就能得到最长子串的大小

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int[] last = new int[128];	// 记录字符最后出现的位置。
        for(int i = 0;i<128;i++){
            last[i] = -1;
        }
        int result = 0;		// 记录最大子串的长度
        int t = 0;			// 记录子串的起始位置
        int index;			// 下标
        for (int i = 0; i < s.length(); i++){
            index = s.charAt(i);
            t = (last[index] + 1) > t? (last[index] + 1):t;
            result = result>(i - t + 1)?result:(i - t + 1);
            last[index] = i;
        }
        return result;
    }
}

4.寻找两个正序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。算法的时间复杂度应该为 O(log (m+n)) 。

解题思路:
使用二分查找确定两个有序数组的「分割线」,中位数就由分割线左右两侧的元素决定;
分割线满足这样的性质:左右两边元素个数相等(这里忽略两个数组长度之和奇偶性的差异);
分割线左边所有元素 小于等于 分割线右边所有元素;
由于分割线两边元素个数相等,挪动分割线就会有「此消彼长」的现象,所以使用二分去定位。

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        // 保证 nums1 数组长度最短,在二分的时候,最快找到分割线。
        if (nums1.length > nums2.length) {
            int[] t = nums1;
            nums1 = nums2;
            nums2 = t;
        }
        int m = nums1.length;
        int n = nums2.length;
        // 整个数组分割线边的元素个数。
        // 如果 m+n是偶数,+1 为奇数,对值没有影响
        // 如果 m+n是奇数,+1 为偶数,则值 + 1;
        // 保证分割线左边的个数比右边多一个,保证中位数在左边。
        int totalLeft = (m + n + 1)/2;
        int left = 0;
        int right = m;
        int i,j;
        // 对 nums1 进行二分查找
        while (left < right) {
            // i为数组nums1的分割线位置
            // j为数组nums2的分割线位置
            i = left + (right - left + 1)/2;
            j = totalLeft - i;
            // i-1为 分割线左边的第一个元素,j-1为 分割线左边的第一个元素
            // 使得所有分割线左边的元素小于右边,即 nums1[i-1]<=nums2[j],nums2[j-1]>= nums[i]
            if(nums1[i-1]>nums2[j]){
            	// 新的区间查找 [0,i-1]
                right = i - 1;
            }else{
            	// [i,right]
                left = i;
            }
        }
        // 上面循环出来,就找到正确的分割线位置
        i = left;
        j = totalLeft - i;
        // 分别考虑 分割线两边的四个元素,求出中位数
        int nums1_MAX = i==0?Integer.MIN_VALUE:nums1[i-1];
        int nums1_MIN = i == m?Integer.MAX_VALUE:nums1[i];
        int nums2_MAX = j==0?Integer.MIN_VALUE:nums2[j-1];
        int nums2_MIN = j == n?Integer.MAX_VALUE:nums2[j];
        // 根据数组的长度奇偶数
        if((m+n)%2==0){
            // 数组长度为偶数,为左边最大值和右边最小值的和除以2
            return (Math.max(nums1_MAX,nums2_MAX)+Math.min(nums1_MIN,nums2_MIN))*1.0/2;
        }else{
        	// 数组长度为奇数,中位数为左边元素的最大值。
            return Math.max(nums1_MAX,nums2_MAX);
        }
    }

5.最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。
解题思路:对于每一个字符,考虑两种情况,回文串是奇数个,回文串是偶数个,做中心扩展,判断是否为回文串,并记录长度。
中心扩散法:

public String longestPalindrome(String s) {
    int start = 0;
    int length = 1;
    int tLength;
    int  l,r;
    for(int i = 0; i<s.length()-1;i++){
    	// 考虑回文串可能是偶数
        l = i;
        r = i + 1;
        while(l >= 0 && r < s.length() && s.charAt(r) == s.charAt(l)){
            r++;
            l--;
        }
        tLength = r - l - 1;	//记录长度
        // 考虑回文串可能是奇数
        l=i-1;
        r=i+1;
        while((l >= 0) && (r < s.length())&&s.charAt(l) == s.charAt(r)){
            r++;
            l--;
        }
        // 判断是否更新回文串
        if(tLength>length || r-l-1 >length){
            length = tLength>r-l-1?tLength:r-l-1;
            start = i - (length - 1) / 2;
        }
    }
    return s.substring(start,start + length);
}

动态规划法:
对于一个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。
根据这样的思路,我们就可以用动态规划的方法解决本题。用P(i,j) 表示字符串 s的第 i 到 j 个字母组成的串是否为回文串;
P(i,j) 是回文串,那么 P(i+1,j-1)也是回文串。
P(i,j) 不是回文串,分为两种情况,P(i+1,j-1)不是回文串,i > j,不合法。
动态规划的状态转移方程:
P(i,j)=P(i+1,j−1)∧(S[i] == s[j])
动态规划的边界条件:
P(i,i) = true , P(i,i+1) = (S[i] == S[ i+1])

图解
单个字符的串一定是回文,标记为true,然后从左向下,再向右,依次填表。
在这里插入图片描述
在这里插入图片描述

public String longestPalindrome(String s) {
    int len = s.length();
    if (len < 2) {
        return s;
    }
    int maxLen = 1;
    int begin = 0;
    // dp[i][j] 表示 s[i..j] 是否是回文串
    boolean[][] dp = new boolean[len][len];
    // 初始化:所有长度为 1 的子串都是回文串
    for (int i = 0; i < len; i++) {
        dp[i][i] = true;
    }
    char[] charArray = s.toCharArray();
    // 递推开始
    // 先枚举子串长度
    for (int j = 1; j < len; j++) {
        // 枚举左边界,左边界的上限设置可以宽松一些
        for (int i = 0; i < j; i++) {
            if (charArray[i] != charArray[j]) {
                dp[i][j] = false;
            } else {
                if (j - i < 3) {	
                	// 回文串长度是2,3的情况,直接可以判断是回文
                    dp[i][j] = true;
                } else {
                	// 参考已有的结果
                    dp[i][j] = dp[i + 1][j - 1];
                }
            }
            //只要dp[i][j] == true,并且长度大于已有的最大回文串,记录回文长度和起始位置
            if (dp[i][j] && j - i + 1 > maxLen) {
                maxLen = j - i + 1;
                begin = i;
            }
        }
    }
    return s.substring(begin, begin + maxLen);
}

7.整数反转

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。

public int reverse(int x) {
    int reversex = 0;
    while(x!=0) {
        if(Integer.MIN_VALUE/10 > reversex || Integer.MAX_VALUE/10 < reversex){
            return 0;
        }
        reversex = reversex * 10 + (x % 10);
        x = x / 10;
    }
    return reversex;
}

8.字符串转换整数 (atoi)

请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数。
函数 myAtoi(string s) 的算法如下:

  • 读入字符串并丢弃无用的前导空格
  • 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
  • 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
  • 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
  • 如果整数数超过 32 位有符号整数范围,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231,大于 231 − 1 的整数应该被固定为 231 − 1 。
  • 返回整数作为最终结果。

解题思路:可以出现的前导符号空格,符号位(只能出现一次),然后就必须是数字(0-9),直到读取到其他字符,读取结束,同时判断是否溢出。

public int myAtoi(String s) {
    int x = 0;
    int i = 0;
    int f = 0;
    char[] cs = s.toCharArray();
    int length = cs.length;
    while(i < length && cs[i] == ' '){
        i++;
    }
    if(i < length && cs[i] == '-'){
        f = -1;
        i++;
    }
    if(i < length && cs[i] == '+'){
        if(f != 0)
            return 0;
        f = 1;
        i++;
    }
    for(;i<length;i++){
        if(cs[i]>='0' && cs[i]<='9'){
            if(x > Integer.MAX_VALUE / 10 || 
            x == Integer.MAX_VALUE / 10   && 
            (cs[i] - '0') > Integer.MAX_VALUE % 10){
                if(f == 1 || f == 0){
                    return Integer.MAX_VALUE;
                }else{
                    return Integer.MIN_VALUE;
                }
            }
            x = x*10 + cs[i]-'0';
        }else{
            break;
        }
    }
    return x*(f!=0?f:1);
}

10.正则表达式匹配

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
. 匹配任意单个字符
*匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖整个字符串 s的,而不是部分字符串。

解题思路:
题目中的匹配是一个「逐步匹配」的过程:我们每次从字符串 p 中取出一个字符或者「字符 + 星号」的组合,并在 s 中进行匹配。对于 p 中一个字符而言,它只能在 s 中匹配一个字符,匹配的方法具有唯一性;而对于 p 中 [ 字符 + 星号 ] 的组合而言,它可以在 s 中匹配任意自然数个字符,并不具有唯一性。

f [i][j] 表示 s 的前 i 个字符与 p 中的前 j 个字符是否能够匹配。

在进行状态转移时,我们考虑 p 的第 j 个字符的匹配情况:
如果 p 的第 j 个字符是一个小写字母,那么我们必须在 s 中匹配一个相同的小写字母。
字母 + 星号的组合在匹配的过程中,本质上只会有两种情况:

  1. 匹配 s 末尾的一个字符,将该字符扔掉,而该组合还可以继续进行匹配;
  2. 不匹配字符,将该组合扔掉,不再进行匹配。

在任意情况下,只要 p[j] 是 .,那么 p[j]一定成功匹配 s 中的任意一个小写字母。

动态规划的边界条件为 f[0][0] = true,即两个空字符串是可以匹配的。最终的答案即为 f[m][n],其中 m 和 n 分别是字符串 s 和 p 的长度。
根据以上三种情况的状态转移方程
在这里插入图片描述
代码:

public boolean isMatch(String s, String p) {
    int m = s.length();
    int n = p.length();
    boolean[][] f = new boolean[m + 1][n + 1];
    f[0][0] = true;
    for(int j = 1;j<=n;j++){
        if (j > 1) {
            if (p.charAt(j-1) == '*') {
                f[0][j] = f[0][j-2];
            }
        } else {
            if (p.charAt(j-1) == '*') {
                f[0][j] = true;
            }
        }
    }
    for (int i = 1; i <= m; ++i) {
        for (int j = 1; j <= n; ++j) {
            if(p.charAt(j-1) == '.'||p.charAt(j-1) == s.charAt(i-1)){
                f[i][j] = f[i-1][j-1];
            }else if(p.charAt(j-1) == '*'){
                if (j > 1) {
                    if (f[i][j-2]) {
                        f[i][j] = true;
                    }else if(p.charAt(j-2) == '.' || p.charAt(j-2) == s.charAt(i-1)){
                        f[i][j] = f[i-1][j];
                    }
                }
            }
        }
    }
    return f[m][n];
}

11.盛最多水的容器

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
就是求两点组成的面积最大。
解题思路:一开始想到的是暴力法,提交超时,就换成双指针法。将指针分别指向首尾两个元素,此时容器的底为最大,如果想让面积最大,只需要移动指向高较短的元素的指针。直到扫描完整个数组元素。

public int maxArea(int[] height) {
    int i = 0;
    int j = height.length-1;
    int area = 0;
    while(i < j) {
        area =  Math.max(Math.min(height[i],height[j])*(j-i),area);
        if(height[i]>height[j]){
            j--;
        }else{
            i++;
        }
    }
    return area;
}

13.罗马数字转整数

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
在这里插入图片描述
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例:
在这里插入图片描述
解题思路:建立映射表,如果罗马数字中小的数字在大的右边,则减去小的数字。

public int romanToInt(String s) {
    Map<Character,Integer> map = new HashMap<>();
    map.put('I',1);
    map.put('V',5);
    map.put('X',10);
    map.put('L',50);
    map.put('C',100);
    map.put('D',500);
    map.put('M',1000);
    int result = 0;
    int x;
    for(int i= 0;i < s.length();i++){
        x = map.get(s.charAt(i));
        if(i < s.length()-1 && map.get(s.charAt(i+1)) > x){
            result -= x;
        }else{
            result += x;
        }
    }
    return result;
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值