跳跃游戏 II
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
假设你总是可以到达数组的最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
示例 2:
输入: [2,3,0,1,4]
输出: 2
提示:
1 <= nums.length <= 1000
0 <= nums[i] <= 105
方法一:
使用辅助数组,来进行动态规划存储,每次存储步数最小。
class Solution {
public int jump(int[] nums) {
//设立一个辅助数组
int [] numsNext = new int [nums.length+1];
//将数组中的元素都填充至最大
Arrays.fill(numsNext,Integer.MAX_VALUE);
//首元素为0(保护),1为0(1为始发点)
numsNext[0]=0;
numsNext[1]=0;
for(int i=1;i<numsNext.length;i++){
//用j来表示后移步,只要在nums【i-1】当前节点的辐射范围内
for(int j=1;j<=nums[i-1]&&(i+j<numsNext.length);j++){
//步数取小
numsNext[j+i]=Math.min(numsNext[j+i],numsNext[i]+1);
}
}
return numsNext[nums.length];
}
}
方法二:
贪心算法,用贪心思想来解决问题。寻求可以抵达目标点最远的点,然后步数+1,更新目标点。
class Solution {
public int jump(int[] nums) {
//标记目标节点
int position = nums.length - 1;
//步数为0
int steps = 0;
//当目标节点没有初始化为首节点时,不断循环
while (position > 0) {
//找出距离目标节点最远的节点
for (int i = 0; i < position; i++) {
if (i + nums[i] >= position) {
//更新目标节点
position = i;
//步数增加
steps++;
break;
}
}
}
return steps;
}
}
方法三:
class Solution {
public int jump(int[] nums) {
//终结节点
int length = nums.length;
//用来保存每次步数的最大长度
int end = 0;
//当前最大长度
int maxPosition = 0;
//步数统计
int steps = 0;
for (int i = 0; i < length - 1; i++) {
maxPosition = Math.max(maxPosition, i + nums[i]);
//当i==end时候,来更新每步的最长长度
if (i == end) {
end = maxPosition;
steps++;
}
}
return steps;
}
}
最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”
示例 3:
输入:s = “a”
输出:“a”
示例 4:
输入:s = “ac”
输出:“a”
提示:
1 <= s.length <= 1000
s 仅由数字和英文字母(大写和/或小写)组成
方法一:
中心扩散:遍历每个节点,考虑到其边界情况,单点中心和双点中心两种方法。
class Solution {
public String longestPalindrome(String s) {
if(s.length()==0){
return s;
}
//使用str来进行最后结果的存储
String str="";
for(int i=0;i<s.length();i++){
//当i节点为中心时
str = longestList(s,str,i,i);
//当i和i+1为中心时
str = longestList(s,str,i,i+1);
}
return str;
}
public String longestList(String s,String str,int left,int right){
//当右指针超过左边界或者左右指针值不同时,直接返回
if(right>=s.length()||s.charAt(left)!=s.charAt(right)){
return str;
}
//当左右指针都在范围区间内则可以循环
while(left>=0&&right<s.length()){
//当左右指针值相同时,扩大范围
if(s.charAt(left)==s.charAt(right)){
left--;
right++;
}else{
//否则直接跳出循环
break;
}
}
//当此回文范围大于已存范围则扩大
if(right-left-1>str.length()){
str = s.substring(left+1,right);
}
return str;
}
}
方法二:
动态规划,借鉴一下官方的,动态规划的关键在于动态区间和动态规划式
public class Solution {
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 L = 2; L <= len; L++) {
// 枚举左边界,左边界的上限设置可以宽松一些
for (int i = 0; i < len; i++) {
// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
int j = L + i - 1;
// 如果右边界越界,就可以退出当前循环
if (j >= len) {
break;
}
if (charArray[i] != charArray[j]) {
dp[i][j] = false;
} else {
if (j - i < 3) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
}
// 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
}
方法三:
“马拉车”算法(Manancher),该算法就是在字符串的每个字符之间插入‘#’然后字符串头部插入‘!’,字符串尾部插入‘?’这个!和?主要是用来起哨兵保护作用,并且左右边界的隔离作用。然后就可以忽略偶数长度的情况,直接考虑奇数的状态即可。最后计算长度时候忽略#。实际就是个优化的中心扩散。
class Solution {
public String longestPalindrome(String s) {
int start = 0, end = -1;
StringBuffer t = new StringBuffer("#");
for (int i = 0; i < s.length(); ++i) {
t.append(s.charAt(i));
t.append('#');
}
t.append('#');
s = t.toString();
List<Integer> arm_len = new ArrayList<Integer>();
int right = -1, j = -1;
for (int i = 0; i < s.length(); ++i) {
int cur_arm_len;
if (right >= i) {
int i_sym = j * 2 - i;
int min_arm_len = Math.min(arm_len.get(i_sym), right - i);
cur_arm_len = expand(s, i - min_arm_len, i + min_arm_len);
} else {
cur_arm_len = expand(s, i, i);
}
arm_len.add(cur_arm_len);
if (i + cur_arm_len > right) {
j = i;
right = i + cur_arm_len;
}
if (cur_arm_len * 2 + 1 > end - start) {
start = i - cur_arm_len;
end = i + cur_arm_len;
}
}
StringBuffer ans = new StringBuffer();
for (int i = start; i <= end; ++i) {
if (s.charAt(i) != '#') {
ans.append(s.charAt(i));
}
}
return ans.toString();
}
public int expand(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
--left;
++right;
}
return (right - left - 2) / 2;
}
}
反转每对括号间的子串
给出一个字符串 s(仅含有小写英文字母和括号)。
请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果。
注意,您的结果中 不应 包含任何括号。
示例 1:
输入:s = “(abcd)”
输出:“dcba”
示例 2:
输入:s = “(u(love)i)”
输出:“iloveu”
解释:先反转子字符串 “love” ,然后反转整个字符串。
示例 3:
输入:s = “(ed(et(oc))el)”
输出:“leetcode”
解释:先反转子字符串 “oc” ,接着反转 “etco” ,然后反转整个字符串。
示例 4:
输入:s = “a(bcdefghijkl(mno)p)q”
输出:“apmnolkjihgfedcbq”
提示:
0 <= s.length <= 2000
s 中只有小写英文字母和括号
题目测试用例确保所有括号都是成对出现的
方法一:
使用两个队列,将字符串中的字符一一放入结果栈中,当碰到‘)’,弹出结果队列尾部元素存放入辅助队列中,当结果队列弹出‘(’时候,将辅助队列的元素都返回至结果队列中。然后正向输出结果队列。
class Solution {
public String reverseParentheses(String s) {
//使用双端队列deque当结果队列,因为作为结果队列需要考虑头尾双向输入输出
Deque<String>queueEnd = new LinkedList<>();
//作为辅助队列,只需要头部输出,尾部输入即可
Queue<String>queueAssist = new LinkedList<>();
//结果用str表示
String str = "";
for(int i=0;i<s.length();i++){
if(s.charAt(i)!=')'){
//character转换成string,尾部输入至queueEnd
queueEnd.offer(String.valueOf(s.charAt(i)));
}else {
//当结果集遇到“)”时候,即可翻转直至遇到“(”
while(!queueEnd.peekLast().equals("(")){
queueAssist.offer(queueEnd.pollLast());
}
//删除“(”
queueEnd.pollLast();
//将辅助队列的值放回结果队列中
while(!queueAssist.isEmpty()){
queueEnd.offer(queueAssist.poll());
}
}
}
//使用string内部函数进行字符串拼接
while(!queueEnd.isEmpty()){
str+=queueEnd.poll();
}
return str;
}
}
时间复杂度O(N^ 2),空间复杂度O(N)
空间优化方法:
此方法思路比较复杂,但是不管是时间还是空间优化都特别明显
class Solution {
public String reverseParentheses(String s) {
Deque<String> stack = new LinkedList<String>();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (ch == '(') {
//stack存放()前的信息
stack.push(sb.toString());
//stringbuffer清除内容
sb.setLength(0);
} else if (ch == ')') {
//()内的内容进行反转
sb.reverse();
//将原来存储的插入到最前面
sb.insert(0, stack.pop());
} else {
sb.append(ch);
}
}
return sb.toString();
}
}
此方法借鉴leetcode方法二,思路十分不错,他在于将‘(’和‘)’之间的字符串切分,然后使用步长为1的step来进行左右翻转遍历。该思想步骤:
class Solution {
public String reverseParentheses(String s) {
//设置字符串s的长度
int n = s.length();
//建立辅助数组用来存放每个'('对应的‘)’来进行下标的存放。
int[] pair = new int[n];
//建立双端队列stack,进行存放括号相对应的下标
Deque<Integer> stack = new LinkedList<Integer>();
for (int i = 0; i < n; i++) {
//如果遇到左括号,则stack来记录左括号的下标
if (s.charAt(i) == '(') {
stack.push(i);
//遇到右下标,则在辅助数组中存放左右括号的下标,数组下标是左括号的话,他存放的就是右括号的下标。
} else if (s.charAt(i) == ')') {
int j = stack.pop();
pair[i] = j;
pair[j] = i;
}
}
//用Stringbuffer来进行字符串的拼接
StringBuffer sb = new StringBuffer();
//初始化起点0和步长1
int index = 0, step = 1;
while (index < n) {
if (s.charAt(index) == '(' || s.charAt(index) == ')') {
//进行分段读取下标
index = pair[index];
//如果完整左右括号内字符遍历完毕,则下跳换号。
step = -step;
} else {
sb.append(s.charAt(index));
}
index += step;
}
return sb.toString();
}
}