前言
所有题目均来自力扣题库中的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;
}
}
知识点:
- 无
原题链接:单词搜索