leetcode20道初级题1~5

这篇博客介绍了五个编程挑战,包括在整数数组中寻找两数之和等于目标值的下标、判断整数是否为回文数、将罗马数字转换为整数、原地移除数组中指定值的元素以及实现字符串中子串查找的strStr()函数。文中提供了多种语言(Python、C、Java)的解决方案,并讨论了KMP算法在解决字符串匹配问题上的应用,以提高效率。

c+python+java三解

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

/*python*/
def twoSum(nums, target):
    lens = len(nums)
    j=-1
    for i in range(1,lens):
        temp = nums[:i]
        if (target - nums[i]) in temp:
            j = temp.index(target - nums[i])
            break
    if j>=0:
        return [j,i]

//c
int* twoSum(int* nums, int numsSize, int target) {
    int i,j;
    int *result=NULL;
    for(i=0;i<numsSize-1;i++)
    {
        for(j=i+1;j<numsSize;j++)
        {
            if(nums[i]+nums[j]==target)
            {
                 result=(int*)malloc(sizeof(int)*2);
                 result[0]=i;
                 result[1]=j;
                 return result;
            }
        }
    }
    return result;
}

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length;
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                if (nums[i] + nums[j] == target) {
                    return new int[]{i, j};
                }
            }
        }
        return new int[0];
    }
}

//java

2

回文数

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。

回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

给定一个罗马数字,将其转换成整数。

  • 例如,121 是回文,而 123 不是。
     public boolean isPalindrome(int x) {
            if (x < 0) {
                return false;
            }
            int help = 1;
            int tmp = x;
            while (tmp >= 10) {
                help *= 10;
                tmp /= 10;
            }
            while (x != 0) {
                if (x % 10 != x / help) {
                    return false;
                }
                x = x % help / 10;
                help /= 100;
            }
            return true;
        }
    
    
    #include <stdio.h>
    #include <math.h>
    #include <stdbool.h>
    bool isPalindrome(int x){
        int n=x,w=0,a[32],i;
        bool ju;
        if (x<0) {
            ju=false;
            return ju;
        }
        else {
            while (n) {
                n/=10;
                w++;
            }
            n=x;
            for (i=0;i<w;i++) {
                a[i]=n/pow(10,w-1-i);//顺序存储
                n-=a[i]*pow(10,w-1-i);
            }
            for (i=0;i<w-1-i;i++) {
                if (a[i]!=a[w-1-i]) {
                    ju=false;
                    break;
                }else {
                    ju=true;
                }
            }
            return ju;
        }
    }
    int main()
    {
        int x;
        scanf("%d",&x);
        printf("%d",isPalindrome(x));
        return 0;
    }
    
    
    class Solution:
        def isPalindrome(self, x: int) -> bool:
            return str(x) == str(x)[::-1]
    
    

    3.

    罗马数字转整数

    难度简单1982

    罗马数字包含以下七种字符: I, V, X, LCD 和 M

    字符          数值
    I             1
    V             5
    X             10
    L             50
    C             100
    D             500
    M             1000

    例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做  XXVII, 即为 XX + V + II 。

    通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

  • I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
  • X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 
  • C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
  • class Solution {
        public int romanToInt(String s) {
            s = s.replace("IV","a");
            s = s.replace("IX","b");
            s = s.replace("XL","c");
            s = s.replace("XC","d");
            s = s.replace("CD","e");
            s = s.replace("CM","f");
            
            int result = 0;
            for (int i=0; i<s.length(); i++) {
                result += which(s.charAt(i));
            }
            return result;
        }
    
        public int which(char ch) {
            switch(ch) {
                case 'I': return 1;
                case 'V': return 5;
                case 'X': return 10;
                case 'L': return 50;
                case 'C': return 100;
                case 'D': return 500;
                case 'M': return 1000;
                case 'a': return 4;
                case 'b': return 9;
                case 'c': return 40;
                case 'd': return 90;
                case 'e': return 400;
                case 'f': return 900;
            }
            return 0;
        }
    }
    class Solution(object):
        def romanToInt(self, s):
            s = s.replace("IV", "a")
            s = s.replace("IX", "b")
            s = s.replace("XL", "c")
            s = s.replace("XC", "d")
            s = s.replace("CD", "e")
            s = s.replace("CM", "f")
            result = 0
    
            for i in range(len(s)):
                result += self.str_to_int(s[i])
            return result
    
        def str_to_int(self, s):
            if s == "I": return 1
            elif s == "V": return 5
            elif s == "X": return 10
            elif s == "L": return 50
            elif s == "C": return 100
            elif s == "D": return 500
            elif s == "M": return 1000
            elif s == "a": return 4
            elif s == "b": return 9
            elif s == "c": return 40
            elif s == "d": return 90
            elif s == "e": return 400
            elif s == "f": return 900
    
    
    int romanToInt(char * s){
        int strLength = strlen(s), flag = 0,sum =  0,flag_1 = 0;
        --strLength;
        for(;strLength >= 0;--strLength){
            switch(*(s+strLength)){
                case 'I': flag = 1;break;
                case 'V': flag = 5;break;
                case 'X': flag = 10;break;
                case 'L': flag = 50;break;
                case 'C': flag = 100;break;
                case 'D': flag = 500;break;
                case 'M': flag = 1000;break;
            }
            flag_1 = flag_1 > flag ? -flag : flag;
            sum += flag_1;
        }
        return sum;
    }
    
    

    4.移除元素

    难度简单1414

    给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

    不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

    元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

  • class Solution {
        public int removeElement(int[] nums, int val) {
            int idx = 0;
            for (int x : nums) {
                if (x != val) nums[idx++] = x;
            }
            return idx;
        }
    }
    
    
    class Solution:
        def removeElement(self, nums: List[int], val: int) -> int:
            a = 0
            b = 0
    
            while a < len(nums):
                if nums[a] != val:
                    nums[b] = nums[a]
                    b += 1
                a += 1
    
            return b
    
    int removeElement(int* nums, int numsSize, int val) {
        int left = 0;
        for (int right = 0; right < numsSize; right++) {
            if (nums[right] != val) {
                nums[left] = nums[right];
                left++;
            }
        }
        return left;
    }
    
    

    5.实现 strStr()

    实现 strStr() 函数。

    给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回  -1 

  • //一脸疑惑
    public class Solution {
        public int StrStr(string haystack, string needle) {
                int n = haystack.Length;
                int m = needle.Length;
    
                for (int i = 0; i <= n-m; i++)
                {
                    int initI = i;
                    bool match = true;
                    for (int j = 0; j < m; j++,i++)
                    {
                        if (haystack[i] != needle[j])
                        {
                            match = false;
                            break;
                        }
                    }
                    
                    if (match)
                    {
                        return initI;
                    }
    
                    i = initI;
                }
                return -1;
        }
    }
    
    
    class Solution {
        public int strStr(String ss, String pp) {
            int n = ss.length(), m = pp.length();
            char[] s = ss.toCharArray(), p = pp.toCharArray();
            for (int i = 0; i <= n - m; i++) {
                int a = i, b = 0;
                while (b < m && s[a] == p[b]) {
                    a++;
                    b++;
                }
                if (b == m) return i;
            }
            return -1;
        }
    }
    
    

    搜了搜:

  • KMP 解法
    KMP 算法是一个快速查找匹配串的算法,它的作用其实就是本题问题:如何快速在「原字符串」中找到「匹配字符串」。

    上述的朴素解法,不考虑剪枝的话复杂度是 O(m * n)O(m∗n) 的,而 KMP 算法的复杂度为 O(m + n)O(m+n)。

    KMP 之所以能够在 O(m + n)O(m+n) 复杂度内完成查找,是因为其能在「非完全匹配」的过程中提取到有效信息进行复用,以减少「重复匹配」的消耗。

    你可能不太理解,没关系,我们可以通过举 🌰 来理解 KMP。

    1. 匹配过程
    在模拟 KMP 匹配过程之前,我们先建立两个概念:

    前缀:对于字符串 abcxxxxefg,我们称 abc 属于 abcxxxxefg 的某个前缀。
    后缀:对于字符串 abcxxxxefg,我们称 efg 属于 abcxxxxefg 的某个后缀。
    然后我们假设原串为 abeababeabf,匹配串为 abeabf:

    我们可以先看看如果不使用 KMP,会如何进行匹配(不使用 substring 函数的情况下)。

    首先在「原串」和「匹配串」分别各自有一个指针指向当前匹配的位置。

    首次匹配的「发起点」是第一个字符 a。显然,后面的 abeab 都是匹配的,两个指针会同时往右移动(黑标)。

    在都能匹配上 abeab 的部分,「朴素匹配」和「KMP」并无不同。

    直到出现第一个不同的位置(红标):

    接下来,正是「朴素匹配」和「KMP」出现不同的地方:

    先看下「朴素匹配」逻辑:
    1. 将原串的指针移动至本次「发起点」的下一个位置(b 字符处);匹配串的指针移动至起始位置。

    2. 尝试匹配,发现对不上,原串的指针会一直往后移动,直到能够与匹配串对上位置。

    如图:

    也就是说,对于「朴素匹配」而言,一旦匹配失败,将会将原串指针调整至下一个「发起点」,匹配串的指针调整至起始位置,然后重新尝试匹配。

    这也就不难理解为什么「朴素匹配」的复杂度是 O(m * n)O(m∗n) 了。

    然后我们再看看「KMP 匹配」过程:
    首先匹配串会检查之前已经匹配成功的部分中里是否存在相同的「前缀」和「后缀」。如果存在,则跳转到「前缀」的下一个位置继续往下匹配:

    跳转到下一匹配位置后,尝试匹配,发现两个指针的字符对不上,并且此时匹配串指针前面不存在相同的「前缀」和「后缀」,这时候只能回到匹配串的起始位置重新开始:

    到这里,你应该清楚 KMP 为什么相比于朴素解法更快:

    因为 KMP 利用已匹配部分中相同的「前缀」和「后缀」来加速下一次的匹配。

    因为 KMP 的原串指针不会进行回溯(没有朴素匹配中回到下一个「发起点」的过程)。

    第一点很直观,也很好理解。

    我们可以把重点放在第二点上,原串不回溯至「发起点」意味着什么?

    其实是意味着:随着匹配过程的进行,原串指针的不断右移,我们本质上是在不断地在否决一些「不可能」的方案。

    当我们的原串指针从 i 位置后移到 j 位置,不仅仅代表着「原串」下标范围为 [i,j)[i,j) 的字符与「匹配串」匹配或者不匹配,更是在否决那些以「原串」下标范围为 [i,j)[i,j) 为「匹配发起点」的子集。

    2. 分析实现
    到这里,就结束了吗?要开始动手实现上述匹配过程了吗?

    我们可以先分析一下复杂度。如果严格按照上述解法的话,最坏情况下我们需要扫描整个原串,复杂度为 O(n)O(n)。同时在每一次匹配失败时,去检查已匹配部分的相同「前缀」和「后缀」,跳转到相应的位置,如果不匹配则再检查前面部分是否有相同「前缀」和「后缀」,再跳转到相应的位置 ... 这部分的复杂度是 O(m^2)O(m 
    2
     ) ,因此整体的复杂度是 O(n * m^2)O(n∗m 
    2
     ),而我们的朴素解法是 O(m * n)O(m∗n) 的。

    说明还有一些性质我们没有利用到。

    显然,扫描完整原串操作这一操作是不可避免的,我们可以优化的只能是「检查已匹配部分的相同前缀和后缀」这一过程。

    再进一步,我们检查「前缀」和「后缀」的目的其实是「为了确定匹配串中的下一段开始匹配的位置」。

    同时我们发现,对于匹配串的任意一个位置而言,由该位置发起的下一个匹配点位置其实与原串无关。

    举个 🌰,对于匹配串 abcabd 的字符 d 而言,由它发起的下一个匹配点跳转必然是字符 c 的位置。因为字符 d 位置的相同「前缀」和「后缀」字符 ab 的下一位置就是字符 c。

    可见从匹配串某个位置跳转下一个匹配位置这一过程是与原串无关的,我们将这一过程称为找 next 点。

    显然我们可以预处理出 next 数组,数组中每个位置的值就是该下标应该跳转的目标位置( next 点)。

    当我们进行了这一步优化之后,复杂度是多少呢?

    预处理 next 数组的复杂度未知,匹配过程最多扫描完整个原串,复杂度为 O(n)O(n)。

    因此如果我们希望整个 KMP 过程是 O(m + n)O(m+n) 的话,那么我们需要在 O(m)O(m) 的复杂度内预处理出 next数组。

    所以我们的重点在于如何在 O(m)O(m) 复杂度内处理处 next 数组。

    3. next 数组的构建
    接下来,我们看看 next 数组是如何在 O(m)O(m) 的复杂度内被预处理出来的。

    假设有匹配串 aaabbab,我们来看看对应的 next 是如何被构建出来的。

    这就是整个 next 数组的构建过程,时空复杂度均为 O(m)O(m)。

    至此整个 KMP 匹配过程复杂度是 O(m + n)O(m+n) 的。

    作者:AC_OIer
    链接:https://leetcode.cn/problems/implement-strstr/solution/shua-chuan-lc-shuang-bai-po-su-jie-fa-km-tb86/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值