Leetcode字符串考察(Python和java实现)

字符串

本章节代码部分参考自网络,部分由自己编写,但都已得到验证,均可正确执行。

0. 字符串去重

要求:

input:hellolo
out:helo

Python

# 方法 1
def filter1():
	string = "hellolo"
	res = ""
	
	for i in range(len(string)):
	    if string[i] not in res:
	        res = res + string[i]
	print(res)

# 方法二
# 使用set()中元素不能重复的特性。但是(去重后会进行排序)。
# 使用list()中元素可以重复的特性作为排序规则进行排序。
# key的作用:我们可以利用它实现按我们想要的标准进行排序,即我们可以自定义排序的标准,或者称为自定义排序的方式。

def filter2():
	string = "hellolo"
	list1 = list(string)
	print(list1.index)
	lists = list(set(list1))
	lists.sort(key=list1.index)
	print(lists)

java 代码

方法1

public String unique_string (String s) {
		
		HashSet<Character> set = new HashSet<Character>();
		StringBuffer stringBuffer= new StringBuffer();
		
		for(int i = 0;i<s.length();i++) {
			if (!set.contains(s.charAt(i))) {
				set.add(s.charAt(i));
				stringBuffer.append(s.charAt(i));
			}	
		}
		return stringBuffer.toString();
    }

方法 2

package Solution;

public class Test {
	
	public String filter(String s) {
		
		if(s==null)
			return null;
		
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < s.length(); i++) {
			char c = s.charAt(i);
			if(s.indexOf(c) == s.lastIndexOf(c)) {  第一次出现的下标跟最后一次相等,表示当前字符没有出现重复
				sb.append(c);
			}
			else {
				if(s.indexOf(c) == i) {
					sb.append(c);
				} //如果重复出现的字符的位置等于当先字符的索引值,即表示当前字符为重复字符出现的第一次,将其加入StringBuilder中
			}
		}
		return sb.toString();
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Test test = new Test();
		String s = "hellolo";
        System.out.println("过滤重复字符之后的字符串:");
        System.out.println(test.filter(s));
	}

}

1. 回文数:【Valid Palindrome】

给定一个字符串,确定它是否是一个回文,只考虑字母数字字符,忽略大小写。
例如:

  • “A man, a plan, a canal: Panama” is a palindrome.
  • “race a car” is not a palindrome.

注意:你是否考虑过这个字符串可能是空的?这是个很好的问题,可以在面试时问。在这个问题上,我们把空字符串定义为有效的回文。

Python

解析:

  • 首先去掉空格其他字符,然后转换大小写.lower()

  • 最后把字符转换过来,看看和原来的一不一样就可以了

def is_palindrome(s):

    s1 = []
    for i in range(len(s)):
        # isalnum()检查一个字符串是否由字母或数字组成
        if s[i].isalnum():  # 在这里我们用isalnum()来判断s[i]字符是不是数字和字母,用来去除其他符号
            s1.append(s[i].lower())
    if s1 == s1[::-1]:  # 翻转后是否与之前的相等
        return True
    else:
        return False


if __name__ == '__main__':
    string = "A man, a plan, a canal: Panama"
    print(is_palindrome(string))

方法二

def is_palindrome2(str):
        l , r = 0 ,len(s)-1
        while l<r:
            while l<r and not s[l].isalnum(): # 不是数字和字符
                l += 1
            while l<r and not s[r].isalnum():
                r -= 1
            if s[l].lower() != s[r].lower(): # 判断对应指针字符是否相同
                return False
            l += 1
            r -= 1
        return True

java 代码

思路:

  • 采用前后指针,不断往前移动即可
  • 如果当前元素不是字母和数字,就跳过继续往前
public boolean isPalindrome(String s) {
		s = s.toLowerCase(); // 把输入字符串中的大写字母全部转换为小写字符

		int start = 0;
		int end = s.length() - 1;

		while (start < end) {

			char left = s.charAt(start); // charAt()方法返回指定索引位置的char值
			char right = s.charAt(end);

			// 判断前后对应指针的字符是不是数字或者字符,用来去除其他符号
			boolean leftFlag = check(left);
			boolean rightFlag = check(right);

			// 两个都是Ture
			if (leftFlag && rightFlag) {

				// 判断他们是否相同
				if (left != right)
					return false;

				// 是,头指针加1,尾指针减一
				start++;
				end--;
			} else {// 两个字符有一个不是Ture
				if (!leftFlag) // 头指针字符不是数字和字符,头指针加1
					start++;
				if (!rightFlag) // 尾指针字符不是数字和字符,尾指针减1
					end--;
			}
		}
		return true;
	}

	public boolean check(char ch) {

		if ((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'))
			return true;

		return false;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		String_Solution solution = new String_Solution();
		
		String string = "A man, a plan, a canal: Panama";
		
		boolean bool = solution.isPalindrome(string);
		
		System.out.println(bool);
	}

2. 字符串匹配【Implement strStr()】

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

例如

Input: haystack = "hello", needle = "ll"
Output: 2

Input: haystack = "aaaaa", needle = "bba"
Output: -1

Python

def find_substr(string, substring):
    fri_index = string.find(substring, 0, len(string))
    last_index = string.rfind(substring)  # 0, len(string)默认

    return fri_index, last_index


if __name__ == '__main__':
    s = "this is string example....wow!!!"
    subs = "is"

    fri, last = find_substr(s, subs)
    print("fri = {}  last = {}".format(fri, last))

输出

fri = 2  last = 5

java代码

解析:

  • 首先,needle字符串长度为0,此时按要求返回0.
  • 正常情况下,使用两层循环,将needle字符串依次与haystack不同位置开始的与needle长度相同的子串进行比较。
    • 外层循环次数为sizeA-sizeB;两者的长度差;
    • 内层循环为 sizeB,needle字符串的长度;【即字串与长字符串匹配的次数】
    • 使用一个标志位 flag,表示 haystack 中能否找到 needle 子串;找到,返回 idx;没找到,继续找。
	public int strStr(String haystack, String needle) {
        
		if (needle.isEmpty()) return 0;
        
        int sizeA = haystack.length(), sizeB = needle.length();

		for (int i = 0; i <= sizeA - sizeB; i++) {
			if (haystack.substring(i, i + needle.length()).equals(needle))
				return i;
		}
		return -1;
    }
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		String_Solution solution = new String_Solution();
		
		String string = "aaaaa";
		String string2 = "bba";
		
		int bool = solution.strStr(string, string2);
		
		System.out.println(bool);
		

	}

方法二

// indexOf 字符串中查找其子串第一次出现的位置,如果没有找到该子串,则返回-1 
public int strStr2(String haystack, String needle) {
        int index=haystack.indexOf(needle);  
        return  index;
    }
            

3. Add Binary

给定两个二进制字符串,返回他们的和(用二进制表示)

def addbin(str1, str2):
    """
    bin()返回一个整数 int 或者长整数 long int 的二进制表示。
    int() 函数用于将一个字符串或数字转换为整型。int(x, base=10) x字符串或数字; base 进制数,默认十进制。
    :param str1:
    :param str2:
    :return:
    """
    print(bin(int(str1, 2) + int(str2, 2)))
    return bin(int(str1, 2) + int(str2, 2))[2:]


if __name__ == '__main__':
    s1 = "11"
    s2 = "1"
    c = addbin(s1, s2)
    print(c)

输出:

0b100
100

4. 最长回文子序列【Longest Palindromic Substring】

思路:

  • 暴力枚举法: BF朴素串模式匹配算法
  • 中心扩散法,时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 动态规划方法
  • manacher 算法

Python

  • 中心扩散法求解
def longest_palindrome(s):
    """
    中心扩展法
        step 1:遍历每个字符,把每个字符当做中心逐步向两边扩展,每扩展一步就得到一个新的子串。
               这里针对输入字符串的长度,扩展方式需要根据长度奇偶性质做判断。
        Step 2:判断子串是否为回文串,更新当前最长回文串
        Step 3:返回最长回文串
    """
    lens = len(s)
    maxlen = 0
    start = 0

    # 长度为奇数   且情况特殊时:例如'aba'
    for i in range(lens):
        j = i - 1  # 以 i 位置为中心, j向左扩散
        k = i + 1  # 以 i 位置为中心, j向右扩散
        while j >= 0 and k < lens and s[j] == s[k]:
            if k - j + 1 > maxlen:
                maxlen = k - j + 1
                start = j  # j 为回文串的首个字符索引
            j -= 1
            k += 1

    # 长度为偶数
    for i in range(lens):
        j = i
        k = i + 1
        while j >= 0 and k < lens and s[j] == s[k]:
            if k - j + 1 > maxlen:
                maxlen = k - j + 1
                start = j
            j -= 1
            k += 1

    if maxlen > 0:
        return s[start:start + maxlen]
    return None


if __name__ == '__main__':
    s1 = "aba"
    print(longest_palindrome(s1))

java 代码

  • 动态规划求解最长回文串

对于一个子串而言,如果它是回文串,并且长度大于 2 2 2,那么将它首尾的两个字母去除之后,它仍
然是个回文串。

  • 例如对于字符串 “ a b a b a ” “ababa” ababa, 如果我们已经知道 “ b a b ” “bab” bab 是回文串,那么 “ a b a b a ” “ababa” ababa 一定是回文串,这是因为它的首尾两个字母都是 “ a ” “a” a

根据这样的思路,我们就可以用动态规划的方法解决本题。我们用 P ( i , j ) P(i, j) P(i,j) 表示字符串 s s s 的第 i i i j j j 个字母组成的串(下文表示成 s [ i : j ] s[i: j] s[i:j]) 是否为回文串:

P ( i , j ) = { T u r e 如果字串 S i ... S j 是回文串 F a l s e other P(i,j)= \begin{cases} Ture& \text{如果字串$S_i$...$S_j是回文串$}\\ False& \text{other} \end{cases} P(i,j)={TureFalse如果字串Si...Sj是回文串other
这里的「其它情况」包含两种可能性:

  • s [ i , j ] s[i, j] s[i,j]本身不是一个回文串;
  • i > j i > j i>j,此时 s [ i , j ] s[i,j] s[i,j]本身不合法。

那么我们就可以写出动态规划的状态转移方程:
P ( i , j ) = P ( i + 1 , j − 1 ) ∧ ( S i = = S j ) P(i,j)= P(i+1,j-1) \wedge (S_i== S_j) P(i,j)=P(i+1,j1)(Si==Sj)
也就是说,只有 s [ i + 1 : j − 1 ] s[i+1 :j - 1] s[i+1:j1] 是回文串,并且 s s s 的第 i i i j j j 个字母相同时, s [ i : j ] s[i: j] s[i:j]才会是回文串。

上文的所有讨论是建立在子串长度大于2的前提之上的,我们还需要考虑动态规划中的边界条件,即子串的长度为1或2。对于长度为1的子串,它显然是个回文串;对于长度为2的子串,只要它的两个字母相同,它就是一个回文串。

因此我们就可以写出动态规划的边界条件:
{ P ( i , i ) = T u r e P ( i , i + 1 ) = ( S i = = S j ) \begin{cases} P(i,i)=Ture& \\ P(i,i+1)=(S_i==S_j) \end{cases} {P(i,i)=TureP(i,i+1)=(Si==Sj)

根据这个思路,我们就可以完成动态规划了,最终的答案即为所有 P ( i , j ) = t r u e P(i,j)= true P(i,j)=true j − i + 1 j - i + 1 ji+1 (即子串长度)的最大值。
注意:在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序。

public String longestPalindromeDynamicProgramming(String s) {
		
		int n = s.length();
		boolean[][] dp = new boolean[n][n];
		String ans = "";
		
		for (int l = 0; l < n; ++l) {   
			for (int i = 0; i + l < n; ++i) {
				int j = i + l;   
				if (l == 0) {
					dp[i][j] = true;
				} else if (l == 1) {
					dp[i][j] = (s.charAt(i) == s.charAt(j));
				} else {
					dp[i][j] = (s.charAt(i) == s.charAt(j) && dp[i + 1][j - 1]);
				}
				if (dp[i][j] && l + 1 > ans.length()) {
					ans = s.substring(i, i + l + 1);
				}
			}
		}
		return ans;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		String_Solution solution = new String_Solution();

		String string = "aba";

		System.out.println(solution.longestPalindromeDynamicProgramming(string));

	}

5. 通配符匹配 【Wildcard Matching】

实现通配符模式匹配,支持 ‘?’ 和 ‘*’ 。‘? ’ 匹配任何单个字符。’*’ 匹配任何序列的字符(包括空序列)。匹配应该覆盖整个输入字符串(不是部分)。

例如

Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "*") → true
isMatch("aa", "a*") → true
isMatch("ab", "?*") → true
isMatch("aab", "c*a*b") → false
def is_match(s: str, p: str):
    """
    带备忘录的递归解法
    :param s:
    :param p:
    :return:
    """

    memo = {}  # 字典类型, 建立备忘录, 避免递归中的重复计算
    ssize, psize = len(s), len(p)

    def help_match(i, j):
        print(memo)
        if (i, j) in memo:
            return memo[(i, j)]

        # 递归基1
        if j == psize:  # 当 j 到尾部时,若文本串s也到尾部,则说明恰好匹配
            memo[(i, j)] = i == ssize
            return memo[(i, j)]

        # 递归基2
        if i == ssize:
            # 当 i==ssize,即文本串 s 到尾部,且 j 不可能为 psize (若是则会进入前一个条件语句);
            # 此时若模式串剩余部分全为"*"则可匹配,否则不匹配。
            if p[j] == '*':
                memo[(i, j)] = help_match(i, j + 1)
            else:
                memo[(i, j)] = False
            return memo[(i, j)]

        if s[i] == p[j] or p[j] == "?":  # 该行是help_match(0, 0)正式执行的第一句
            memo[(i, j)] = help_match(i + 1, j + 1)
        elif p[j] == "*":  # 如果字串这时的位置再一个 *
            # "*" 匹配空字符串或一个字符(由于递归,可以代表匹配多个字符的字符串)
            memo[(i, j)] = help_match(i + 1, j) or help_match(i, j + 1)
        else:
            memo[(i, j)] = False
        return memo[(i, j)]

    return help_match(0, 0)


if __name__ == '__main__':
    s1 = "aaba"
    s2 = "*?a"

    print(is_match(s1, s2))
{}
{}
{}
{}
{}
{}
{(4, 1): False, (4, 0): False}
{(4, 1): False, (4, 0): False}
{(4, 1): False, (4, 0): False, (4, 2): False, (3, 1): False, (3, 0): False}
{(4, 1): False, (4, 0): False, (4, 2): False, (3, 1): False, (3, 0): False}
{(4, 1): False, (4, 0): False, (4, 2): False, (3, 1): False, (3, 0): False}
True
def isMatch(s, p):
    m, n = len(s), len(p)
    # 状态数组,表示从 s 的 i 到 p 的 j 是否可以匹配,初始化,m+1 行 n+1 列 False
    dp = [[False for j in range(n + 1)] for i in range(m + 1)]
    dp[0][0] = True
    # 当 s 为空时必须有 * 才可能满足匹配,并且他的真值一定和去掉 * 及其前面的符号相同
    for j in range(1, n + 1):  # 1. 更新第START行的START列,即dp[0]行
        dp[0][j] = p[j - 1] == '*' and dp[0][j - 1]

    for i in range(1, m + 1):  # 2. 更新后面几行,s字符串正式与通配符字符串开始匹配,
        for j in range(1, n + 1):
            # 当 p上一个字符为'?'或者p上一个字符等于s上一个字符,则当前真值与上一位相同
            if p[j - 1] == s[i - 1] or p[j - 1] == '?':
                dp[i][j] = dp[i - 1][j - 1]
            # p上一个字符为'*'时,则*可表示p往后走一位或者s往后走一位
            elif p[j - 1] == '*':
                dp[i][j] = dp[i - 1][j] or dp[i][j - 1]
    # 返回s[0->len(s)] 与 p[0->len(p)]的真值
    print(dp)
    return dp[m][n]


if __name__ == '__main__':
    s1 = "aaba"
    s2 = "*ba"
    print(isMatch(s1, s2))
  • 对于 *,它的值从上一行的值和 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1] 来确定,有一个真,就为: T u r e Ture Ture
  • 对于 ?,它的值从 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1] 变到 d p [ i ] [ j ] dp[i][j] dp[i][j]
  • 注意: d p dp dp数组的0行,0列属于过渡,没有字符串匹配。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值