刷题记录7---最长回文子串+下一个排列+搜索旋转排序数组+在排序数组中查找元素的第一个和最后一个位置+单词搜索

前言

所有题目均来自力扣题库中的hot 100,之所以要记录在这里,只是方便后续复习

5.最长回文子串

题目:
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”
解题思路:
【中心扩散法】回文子串的特点是:从中心位置向两边扩散,每个位置的值都相等。根据这一性质,我们可以遍历字符串,每次都将当前位置的字符当作中心,再字符串有效范围内用两个指针向两边进行扩散,扩散到两个指针不等或超出范围为止,此时更新最长结果值。但这种方法在回文字符串为基数时是可以的,例如bab;偶数时则不行,例如abba,原因时其中心位置并不是一个单独的字符,而是b和b之间,之前的方法就会漏掉这种情况,那如何解决呢?我们在每个字符之间都加上一个特殊字符就可以了
代码(python):

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        # 定义新的字符串,每个字符前后都插入一个 特殊字符*
        new_s = "*"
        for c in s:
            new_s = new_s + c + "*"
        # 定义结果值
        result = ""
        # 遍历新字符串
        for i in range(0, len(new_s)):
            # 定义左右指针,初始都在当前位置
            left = i
            right = i
            # 当左右指针都没超出 新字符有效范围内时,根据中心位置进行扩散
            while left >= 0 and right < len(new_s):
                # 如果左右指针的值 相等
                if new_s[left] == new_s[right]:
                    # 判断左右指针之间的长度是否大于当前结果值长度,若大于则更新结果值
                    if right - left + 1 > len(result):
                        result = new_s[left: right + 1]
                    # 进行扩大范围,即左指针左滑,有指针右滑
                    left -= 1
                    right += 1
                # 如果左右指针不相等了,则不需要在扩散,即中止循环
                else:
                    break
        # 最后将新字符串中的特殊字符 * 都去掉 返回即可
        result = result.replace("*", "")
        return result
            

代码(java):

class Solution {
    public String longestPalindrome(String s) {
        String newS = "#";
        for(int i=0; i<s.length(); i++){
            newS = newS + s.charAt(i) + "#";
        }
        String max_s = "";
        for(int i=0; i<newS.length(); i++){
            int left = i;
            int right = i;
            while(left>=0 && right<newS.length() && newS.charAt(left) == newS.charAt(right)){
                left--;
                right++;
            }
            if(newS.substring(left+1, right).length() > max_s.length()){
                max_s = newS.substring(left+1, right);
            }
        }
        max_s = max_s.replace("#", "");
        return max_s;
    }
}

知识点:

  • python中字符截取可以用str[start: end]方式,包括start位置但不包括end位置,注意start和end之间是冒号而不是逗号

原题链接:最长回文子串

31.下一个排列

题目:
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。
必须 原地 修改,只允许使用额外常数空间。

解题思路:
【两次扫描】我们要找到一个较小数和一个较大数进行交换,并且较小数要尽可能靠右,较大数尽可能小,然后将较小数位置后面的列表进行反转即可得到结果;如何找到较小数?从后向前遍历,找到第一个i,使得该位置的值小于i +1位置的值;如何找到较大数?在找到较小数的前提下,从后向前遍历,找到第一个比i位置的值大的数,即为较大数,交换较小数和较大数。如果没有找到较小数,我们就略过寻找较大数;
代码(python):

class Solution(object):
    def nextPermutation(self, nums):
        """
        :type nums: List[int]
        :rtype: None Do not return anything, modify nums in-place instead.
        """
        # 从后向前遍历,找到第一个i 符合 nums[i] < nums[i + 1] 此时i就是较小数
        i = len(nums) - 2
        while i >= 0 and nums[i] >= nums[i + 1]:
            i -= 1
        # 如果找到了较小数, 从后向前遍历,找到一个 比较小数 大的数  此时j就是较大数
        if i >= 0:
            j = len(nums) - 1
            while j >= 0 and nums[i] >= nums[j]:
                j -= 1
            # 将较小数 和 较大数 交换位置
            self.swap(nums, i, j)
        #将i 即原较小数  的数后面的列表进行原地反转后,就得到了结果
        self.reverse(nums, i + 1, len(nums) - 1)
        return nums
        
    def swap(self, nums, i, j):
        k = nums[i]
        nums[i] = nums[j]
        nums[j] = k
    
    def reverse(self, nums, start, end):
        #列表原地反转可以 由两侧开始,向中间进行,将两侧对应的值进行交换 
        while start < end:
            self.swap(nums, start, end)
            start += 1
            end -= 1

代码(java):

class Solution {
    public void nextPermutation(int[] nums) {

        int i = nums.length - 2;
        while(i >= 0 && nums[i] >= nums[i + 1]){
            i --;
        }
        if (i >= 0){
            int j = nums.length - 1;
            while (j >= 0 && nums[i] >= nums[j]){
                j --;
            }
            swap(nums, i, j);
        }

        int left = i + 1;
        int right = nums.length - 1;
        while(left < right){
            swap(nums, left, right);
            left ++;
            right --;
        }
    }

    public void swap(int[] nums , int i, int j){
        int k = nums[i];
        nums[i] = nums[j];
        nums[j] = k;
    }
}

知识点:

  • 列表原地反转可以 由两侧开始,向中间进行,将两侧对应的值进行交换

原题链接:下一个排列

33.搜索旋转排序数组

题目:
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:
输入:nums = [1], target = 0
输出:-1
解题思路:
【二分查找】数组有序,要寻找目标值,可以联想到二分查找;但是又经过旋转,这就意味着我们找到中间位置后,左边界到中间位置 、中间位置到右边界不都是有序的,但是至少有一个是有序的;我们可以先判断左边界到中间位置是否是有序的(即左边界的值是否小于等于中间位置的值),若有序再看target是否在左边界和中间位置之间,若在则再此区间继续查找,若不在则在右侧区间继续查找,若左边界到中间位置是无序的则中间位置就是有序的,再看target是否在中间位置和右边界之间,若在则再次区间继续查找,若不在则在左侧区间继续查找。总结起来就是先判断哪个区间有序就优先是否在这个区间,不在则查找另一个区间
代码(python):

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        return self.binary_search(nums, 0, len(nums) - 1, target)

    def binary_search(self, nums, start, end, target):
        if start > end:
            return -1
        if start == end:
            if target == nums[start]:
                return start
            else:
                return -1;
        
        mid = (start + end) // 2
        if nums[mid] == target:
            return mid
        print str(start)  + "   " + str(end) + "   " +  str(mid)
        if nums[start] < nums[mid]:
            if nums[start] <= target and nums[mid] >= target:
                return self.binary_search(nums, start, mid , target)
            else:
                return self.binary_search(nums, mid + 1, end, target)
        else:
            if nums[mid + 1] <= target and nums[end] >= target:
                return self.binary_search(nums, mid + 1, end, target)
            else:
                return self.binary_search(nums, start, mid , target)

代码(java):

class Solution {
    public int search(int[] nums, int target) {
        // 如果数组长度为1,判断该值是否和target相等,相等即0,不相等即-1
        if(nums.length == 1){
            return nums[0] == target ? 0: -1;
        }
        //调用二分查找
        return binarySearch(nums, target, 0, nums.length - 1);
    }

    public int binarySearch(int[] nums, int target, int left, int right){
        // 二分查找的中止条件,左边界小于等于右边界
        while (left <= right){
            // 寻找中间位置,判断是否等于target,若相等则返回
            int mid = (left + right) / 2;
            if(nums[mid] == target){
                return mid;
            }
            // 判断left 到 mid 是否是有序的,若有序
            if(nums[left] <= nums[mid]){
                // 判断target 是否在 left 到mid 之间,如果在则在左侧寻找,右边界等于mid - 1(注意要-1)
                if(nums[left] <= target && target < nums[mid]){
                    right = mid - 1;
                // 若target不在 left 和mid 之间, 则在右侧寻找,左边界等于 mid + 1
                }else{
                    left = mid + 1;
                }
            // 如果left 到mid不是有序的,那么mid 到right就是有序的
            }else{
                // 判断target是否在mid 和right 之间,若在则在右侧寻找, 左边界等于mid + 1
                if(nums[mid] < target && target <= nums[right]){
                    left = mid + 1;
                // 若target不在mid 和right之间,则在左侧寻找, 右边界等于mid - 1
                }else{
                    right = mid - 1;
                }
            }
        }
        // 直到left 和 right相遇也没找到,则是没有 返回-1
        return -1;
    }
}

知识点:

  • java中除法取整可以用 / ; python用 //

原题连接:搜索旋转排序数组

34.在排序数组中查找元素的一个和最后一个位置

题目:
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
解题思路:
【二分查找】排序数组、以及寻找元素、以及Onlogn可以联想到二分查找;寻找target的第一个位置可以转化为二分查找第一个大于等于target的值位置,寻找target最后一个位置可以转化为二分查找第一个大于target的值位置再-1,且由于两个寻找条件相似,可以封装在一个函数里;找到两个位置后要进行校验,第一个位置要小于等于最后一个位置并且,第一个位置的值等于target(防止没找到target),校验通过就返回两个位置
代码(python):

class Solution(object):
    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        # 判断列表长度是否为0,若为0直接返回
        if len(nums) == 0:
            return [-1, -1]
        # 通过二分查找 找到 第一个 大于等于 target 的 索引, 即第一个  target 的索引
        left_index = self.binary_search(nums, target, True)
        # 通过二分查找 找到 第一个 大于 target的索引 后,再 -1 就是最后一个 target的索引
        right_index = self.binary_search(nums, target, False) - 1
        # 判断 两个索引是否符合 规范: 左边界小于等于右边界 ,并且 左边界的值是 target ,符合则返回结果
        if left_index <= right_index and nums[left_index] == target:
            return [left_index, right_index]
        # 不符合规范则返回-1, -1
        return [-1, -1]


    # 二分查找方法, 当flag等于True时:寻找第一个 大于等于 target的值索引;False时:第一个 大于target的值索引
    def binary_search(self, nums, target, flag):
        # 初始化左右边界
        left = 0
        right = len(nums) - 1
        # 初始化索引,注意时数组的长度,而不是0, 也不是length-1,
        #因为当寻找第一个大于target的值索引 且并且数组中不存在大于target值时,此时返回的应该时数组长度,这样-1 才是length - 1,即最后一位
        res = len(nums)
        
        while left <= right:
            mid = (left + right) // 2
            # 当中间位置的值大于target时,或者  当寻找的是第一个大于等于target的值且中间位置的值大于等于target时
            if (nums[mid] > target) or (flag  and nums[mid] >= target):
                # 在左侧继续寻找
                right = mid - 1
                #更新结果值为 中间位置, 这就样慢慢向左侧寻找是否有大于 或 大于等于 target的值,最终得到最左侧的值
                res = mid
            # 若中间位置的值小于target 值,则向左侧寻找
            else:
                left = mid + 1
        return res

代码(java):

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int index = binarySearch(nums, target);
        System.out.println(index);
        if (index == -1){
            return new int[]{-1, -1};
        }else{
            int left = index;
            int right = index;
            while (left >= 0 && right <= nums.length -1){
                if(left > 0 && nums[left - 1] == target){
                    left --;
                }else if(right < nums.length - 1 && nums[right + 1] == target){
                    right ++ ;
                }else{
                    break;
                }
            }
            return new int[]{left, right};
        }
    }

    public int binarySearch(int[] nums, int target){
        int start =0;
        int end = nums.length - 1;
        int mid = 0;

        while (start <= end){
            mid = (start + end ) / 2;
            if (nums[mid] == target){
                return mid;
            }else if(nums[mid] > target){
                end = mid - 1;
            }else{
                start = mid + 1;
            }
        }
        return -1;
    }
}

知识点:

  • java中除法取整可以用 / ; python用 //

原题连接:在排序数组中查找元素的第一个和最后一个位置

79.单词搜索

题目:
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
单词搜索示意图

输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true
示例 2:
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “SEE”
输出:true
示例 3:
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCB”
输出:false
解题思路:
【搜索回溯】虽然这道题很像题目最小路径和,让人联想起动态规划,但是移动方式是上下左右都可以,每个位置的值都与上下左右有关系,所以没办法定义初始值且按照一定的方向进行递推,所以不能采用动态规划;按照正常人工去寻找思路,我们会遍历每个网格位置的值,对比是否和单词的第一位相等,若不等则跳过该位置,相等便会在他的上下左右去对比单词的下一位,就这样以此类推,直到单词的最后一位都找到或者表格都遍历完,由于寻找单词每一位的思路都是相同的,只不过对于在表格中的位置不同,所以可以采用递归;还有一点是人工寻找会自动跳过那些已经选取过的位置,那代码中该如何规避那些选过的值呢?可以将选过的值置为一个特殊值保证一定不会再被匹配上,需要注意的是,无论最终此条路径是否找到单词,都要将该位置还原成原来的值,因为在后续的寻找过程中可能还会用到该位置。欸,这不就是搜索回溯的思路嘛
代码(python):

class Solution(object):
    def exist(self, board, word):
        """
        :type board: List[List[str]]
        :type word: str
        :rtype: bool
        """
        # 遍历网格每个位置,查看以该位置为起始点寻找 是否能找到该单词
        for i in range(0, len(board)):
            for j in range(0, len(board[0])):
                result = self.test(board, i, j, word, 0)
                # 如果找到了,则直接返回True
                if result:
                    return True
        return False

    def test(self, board, i, j, word, index):
        # 如果当前寻找的是单词的最后一位,直接返回当前位置是否等于单词最后一位即可
        # 注意这里的判断条件是单词最后一位,而不是index == len(word)
        if index == len(word) - 1:
            return board[i][j] == word[index]

        # 如果当前网格位置的值 等于 寻找的单词对应位置的值
        if board[i][j] == word[index]:
            #将 该位置的值 置为特殊字符,防止二次使用
            board[i][j] = 0
            # 通过递归判断以该网格上下左右为起始点,是否能找到单词剩余的值  注意要判断是否有上下左右位置
            left_result = j > 0 and self.test(board, i, j - 1, word, index + 1)
            right_result = j < len(board[0]) - 1 and self.test(board, i, j + 1, word, index + 1)
            up_result = i > 0 and self.test(board, i - 1, j, word, index + 1) 
            down_result = i < len(board) - 1 and self.test(board, i + 1, j, word, index + 1)
            # 注意要回溯该位置的值,为了后续再寻找时使用该值
            board[i][j] = word[index]
            # 返回上下左右结果
            return left_result or right_result or up_result or down_result
        #  如果当前网格位置的值 不等于 寻找的单词对应位置的值 则直接返回False
        else:
            return False


代码(java):

class Solution {
    public boolean exist(char[][] board, String word) {

        for(int i = 0; i < board.length; i++){
            for(int j = 0; j < board[0].length; j++){
                if(board[i][j] == word.charAt(0)){
                    if(test(board, word, 0, i, j)){
                        return true;
                    }
                }
            }
        }

        return false;
    }

    public boolean test(char[][] board, String word, int index, int i, int j){
        if(word.length() - 1 == index){
            return board[i][j] == word.charAt(index);
        }

        if(board[i][j] != word.charAt(index)){
            return false;
        }

        char val = board[i][j];
        board[i][j] = '#';

        if(i < board.length - 1 && test(board, word, index + 1, i + 1, j)){
            return true;
        }
        if(i > 0 && test(board, word, index + 1, i - 1, j)){
            return true;
        }
        if(j < board[0].length - 1 && test(board, word, index + 1, i, j + 1)) {
            return true;
        }
        if(j > 0 && test(board, word, index + 1, i, j - 1)){
            return true;
        }

        board[i][j] = val;

        return false;
    }


}

知识点:

原题链接:单词搜索

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值