输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
提示:
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100
1 状态定义: 设动态规划列表 dp ,dp[i] 代表以元素 nums[i] 为结尾的连续子数组最大和;
2 转移方程: 若dp[i−1]≤0 ,说明 dp[i−1] 对 dp[i] 产生负贡献,即 dp[i−1]+nums[i] 还不如 nums[i] 本身大:
3 初始状态: dp[0]=nums[0],即以 nums[0] 结尾的连续子数组最大和为 nums[0] ;
4 返回值: 返回 dp 列表中的最大值,代表全局最大值;
class Solution {
public int maxSubArray(int[] nums) {
int pre = 0, max = Integer.MIN_VALUE;
for(int num : nums) {
pre = Math.max(num, pre+num);
max = Math.max(max, pre);
}
return max;
}
}
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:“abc”
输出:3
解释:三个回文子串: “a”, “b”, “c”
示例 2:
输入:“aaa”
输出:6
解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”
提示:
class Solution {
public int countSubstrings(String s) {
int n = s.length();
boolean[][] dp = new boolean[n][n];
int res = 0;
for (int j=0; j<n; j++) {
for (int i=0; i<=j; i++) {
if (s.charAt(i)==s.charAt(j) && (j-i<3 || dp[i+1][j-1])) {
dp[i][j] = true;
res++;
}
}
}
return res;
}
}
复杂性分析
时间复杂度为 O(N2),空间复杂度为 O(N2)。
两种情况:
class Solution {
public int countSubstrings(String s) {
int res = 0;
for (int i=0; i<=s.length()*2-2; i++) {
int left = i / 2;
int right = (i + 1) / 2;
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
res++;
left--;
right++;
}
}
return res;
}
}
复杂性分析
时间复杂度为 O(N2),空间复杂度为 O(1)。
给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
请你找出符合题意的 最短 子数组,并输出它的长度。
示例 1:
输入:nums = [2,6,4,8,10,9,15]
输出:5
解释:你只需要对 [6, 4, 8, 10, 9]进行升序排序,那么整个表都会变为升序排序。
示例 2:
输入:nums = [1,2,3,4]
输出:0
示例 3:
输入:nums = [1]
输出:0
提示:
进阶:你可以设计一个时间复杂度为 O(n) 的解决方案吗?
解题思路:
从左向右遍历,只要碰到比已经遍历过路径内的最大值要小的元素,说明该元素需要被纳入到重排序的子数组中,最终得到右边界点;再从右向左遍历,只要碰到比已经遍历过的路径内的最小值还要大的元素,说明该元素需要被纳入到重排序的子数组中,最终得到左边界点。
class Solution {
public int findUnsortedSubarray(int[] nums) {
// max为遍历过程中最大值的下标,right为目标边界的右边界
int right = 0, max = 0;
for (int i=1; i<nums.length; i++) {
if (nums[i] >= nums[max]) max = i;
else right = i;
}
// min为遍历过程中最小值的下标,left为目标边界的左边界
int left = nums.length - 1, min = nums.length - 1;
for (int i=nums.length-2; i>=0; i--) {
if (nums[i] <= nums[min]) min = i;
else left = i;
}
return right <= left ? 0 : right - left + 1;
}
}
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
提示:
1 状态定义:
dp[i][j]表示从数组的[0,i]区间内挑选一些正整数,每个数只能使用一次,使得这些数的和恰好等于j。
2 初始化
if (nums[0] > target) return false;
else dp[0][nums[0]] = true;
3 状态转移方程:
for (int i = 1; i < len; i++) {
for (int j = 1; j <= target; j++) {
// 1 不用nums[i]
dp[i][j] |= dp[i - 1][j];
// 2 用nums[i],前提是j >= nums[i]
if (j >= nums[i]) {
dp[i][j] |= (j == nums[i]) ? true : dp[i - 1][j - nums[i]];
}
}
}
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) sum += num;
if (sum % 2 == 1) return false;
int target = sum / 2;
// dp[i][j]表示从数组[0, i]区间内挑选一些正整数,每个数最多使用一次,使得这些数的和恰好等于j,是否成立
boolean[][] dp = new boolean[nums.length][target+1];
// 初始化判断
if (nums[0] > target) return false;
dp[0][nums[0]] = true;
for (int i=1; i<nums.length; i++) {
for (int j=1; j<=target; j++) {
// 1 不用nums[i]
dp[i][j] |= dp[i-1][j];
// 2 用nums[i],前提是j >= nums[i]
if(j >= nums[i]) {
dp[i][j] |= j == nums[i] ? true : dp[i-1][j-nums[i]];
}
}
}
return dp[nums.length-1][target];
}
}
给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
示例 1:
输入: 2
输出: [0,1,1]
示例 2:
输入: 5
输出: [0,1,1,2,1,2]
进阶:
class Solution {
public int[] countBits(int n) {
int[] res = new int[n+1];
for (int i=1; i<=n; i++) {
if (i % 2 == 0) res[i] = res[i / 2];
else res[i] = res[i - 1] + 1;
}
return res;
}
}
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]
3
/ \
4 5
/ \ \
1 3 1
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
动态规划+后序遍历:
打劫节点A分两种情况:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int rob(TreeNode root) {
int[] rootStatus = dfs(root);
return Math.max(rootStatus[0], rootStatus[1]);
}
public int[] dfs(TreeNode node) {
if (node == null) {
return new int[]{0, 0};
}
int[] l = dfs(node.left);
int[] r = dfs(node.right);
int selected = node.val + l[1] + r[1];
int notSelected = Math.max(l[0], l[1]) + Math.max(r[0], r[1]);
return new int[]{selected, notSelected};
}
}
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
示例 4:
输入:coins = [1], amount = 1
输出:1
示例 5:
输入:coins = [1], amount = 2
输出:2
提示:
动态规划:
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i=1; i<=amount; i++) {
for (int j=0; j<coins.length; j++) {
if (i - coins[j] >= 0 && dp[i-coins[j]] != Integer.MAX_VALUE) dp[i] = Math.min(dp[i-coins[j]] + 1, dp[i]);
}
}
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}
}
有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。
求所能获得硬币的最大数量。
示例 1:
输入:nums = [3,1,5,8]
输出:167
解释: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> [] coins = 315 + 358 + 138 + 181 = 167
示例 2:
输入:nums = [1,5]
输出:10
提示:
动态规划
class Solution {
public int maxCoins(int[] nums) {
if(nums == null || nums.length == 0) {
return 0;
}
int n = nums.length;
//处理边界:创建一个n+2的数组,开头和末尾都填1
int[] arr = new int[n+2];
Arrays.fill(arr, 1);
for(int i=0; i<n; i++){
arr[i+1] = nums[i];
}
int[][] dp = new int[n+2][n+2];
//dp[i][j]为(i,j)开区间内最大值
for(int j=2; j<=n+1; j++) {
for(int i=j-2; i>=0; i--) {
for(int k=i+1; k<j; k++) {//戳破第k个气球
dp[i][j] = Math.max(dp[i][j], arr[i] * arr[k] * arr[j] + dp[i][k] + dp[k][j]);
}
}
}
return dp[0][n+1];
}
}
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
动态规划:
class Solution {
public int maxProfit(int[] prices) {
if (prices==null || prices.length==0) {
return 0;
}
int n = prices.length;
// dp[i][0]: 手上持有股票的累计最大收益
// dp[i][1]: 手上不持有股票,并且进入冷冻期中的累计最大收益
// dp[i][2]: 手上不持有股票,并且不进入冷冻期中的累计最大收益
int[][] dp = new int[n][3];
dp[0][0] = -prices[0];
for (int i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - prices[i]);
dp[i][1] = dp[i - 1][0] + prices[i];
dp[i][2] = Math.max(dp[i - 1][1], dp[i - 1][2]);
}
return Math.max(dp[n - 1][1], dp[n - 1][2]);
}
}
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为4。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
进阶:
解题思路:动态规划+二分查找
新建数组 cell,用于保存最长上升子序列;
对原序列进行遍历,将每位元素二分插入 cell 中:
cell 未必是真实的最长上升子序列,但长度是对的;
代码
class Solution {
public int lengthOfLIS(int[] nums) {
List<Integer> cell = new ArrayList<>();
cell.add(nums[0]);
for (int i=1; i<nums.length; i++) {
if (nums[i] > cell.get(cell.size()-1)) {
cell.add(nums[i]);
} else {
int left = 0, right = cell.size() - 1;
while (left < right) {
int mid = (left + right) / 2;
if (cell.get(mid) < nums[i]) left = mid + 1;
else right = mid;
}
cell.remove(left);
cell.add(left, nums[i]);
}
}
return cell.size();
}
}
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
给你一个整数n,返回和为n的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
示例 2:
输入:n = 13
输出:2
解释:13 = 4 + 9
提示:
动态规划:
class Solution {
public int numSquares(int n) {
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j * j <= i; j++) {
dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
}
}
return dp[n];
}
}
在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内,找到只包含 ‘1’ 的最大正方形,并返回其面积。
示例 1:
输入:matrix =
[[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“1”,“1”,“1”,“1”],[“1”,“0”,“0”,“1”,“0”]]
输出:4
示例 2:
输入:matrix = [[“0”,“1”],[“1”,“0”]]
输出:1
示例 3:
输入:matrix = [[“0”]]
输出:0
提示:
动态规划:
class Solution {
public int maximalSquare(char[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int maxSide = 0;
int rows = matrix.length, columns = matrix[0].length;
int[][] dp = new int[rows][columns];
for (int i=0; i<rows; i++) {
for (int j=0; j<columns; j++) {
if (matrix[i][j] == '1') {
if (i == 0 || j == 0) dp[i][j] = 1;
else dp[i][j] = Math.min(Math.min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1]) + 1;
}
maxSide = Math.max(maxSide, dp[i][j]);
}
}
return maxSide * maxSide;
}
}
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
class Solution {
public int rob(int[] nums) {
int prev_prev = 0;
int prev = 0;
// 每次循环,计算“偷到当前房子为止的最大金额”
for (int num : nums) {
// 循环开始时,prev表示 dp[k-1],prev_prev表示 dp[k-2]
// dp[k] = max{dp[k-1], dp[k-2] + num}
int temp = Math.max(prev, prev_prev + num);
prev_prev = prev;
prev = temp;
// 循环结束时,prev 表示 dp[k],prev_prev 表示 dp[k-1]
}
return prev;
}
}
给定一个整数数组 nums,求出数组从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点。
实现 NumArray 类:
示例:
输入:
[“NumArray”, “sumRange”, “sumRange”, “sumRange”]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1))
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))
提示:
前缀和:
class NumArray {
int[] preSum;
public NumArray(int[] nums) {
int n = nums.length;
preSum = new int[n + 1];
for (int i = 0; i < n; i++) {
preSum[i + 1] = preSum[i] + nums[i];
}
}
public int sumRange(int left, int right) {
return preSum[right + 1] - preSum[left];
}
}
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
class Solution {
public int maxProduct(int[] nums) {
int maxF = nums[0], minF = nums[0], ans = nums[0];
for (int i = 1; i < nums.length; i++) {
int mx = maxF, mn = minF;
maxF = Math.max(mx * nums[i], Math.max(nums[i], mn * nums[i]));
minF = Math.min(mn * nums[i], Math.min(nums[i], mx * nums[i]));
ans = Math.max(maxF, ans);
}
return ans;
}
}
给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。
示例 2:
输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。 注意你可以重复使用字典中的单词。
示例 3:
输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false
代码
public class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
// dp[i]表示s的前i个位是否可以用wordDict中的单词表示
boolean[] dp = new boolean[s.length() + 1];
// 设定边界条件,假定空字符串可以被表示
dp[0] = true;
// 遍历字符串的所有子串,遍历开始索引i,遍历区间[0,n)
for (int i = 0; i < s.length(); i++) {
// 遍历结束索引j,遍历区间[i+1,n+1)
for (int j = i+1; j <= s.length(); j++) {
if (dp[i] && wordDict.contains(s.substring(i, j))) {
dp[j] = true;
}
}
}
return dp[s.length()];
}
}
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
示例 1:
输入:n = 3
输出:3
示例 2:
输入:n = 11
输出:0
限制:
0 <= n < 2 ^ 31
根据以上分析,可将求解分为3步:
digit, start, count = 1, 1, 9
while n > count:
n -= count
start *= 10 # 1, 10, 100, ...
digit += 1 # 1, 2, 3, ...
count = 9 * start * digit # 9, 180, 2700, ...
num = start + (n - 1) / digit
res = Long.toString(num).charAt((n - 1) % digit) - '0';
class Solution {
public int findNthDigit(int n) {
int digit = 1;
long start = 1;
long count = 9;
while (n > count) {
n -= count;
digit += 1;
start *= 10;
count = digit * start * 9;
}
long num = start + (n - 1) / digit;
return String.valueOf(num).charAt((n - 1) % digit) - '0';
}
}
写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
示例:
输入: a = 1, b = 1
输出: 2
提示:
a, b 均可能是负数或 0
结果不会溢出 32 位整数
a(i) | b(i) | 无进位和n(i) | 进位c(i+1) |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 1 |
答:在计算机系统中,数值一律用补码来表示和存储。补码的优势:加法、减法可以统一处理(CPU只有加法器)。因此,以上方法同时适用于正数和负数的加法。
class Solution {
public int add(int a, int b) {
while(b != 0) { // 当进位为 0 时跳出
int c = (a & b) << 1; // c = 进位
a ^= b; // a = 非进位和
b = c;
}
return a;
}
}
请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
示例 1:
输入:00000000000000000000000000001011
输出:3
示例 2:
输入:11111111111111111111111111111101
输出:31
方法:逐位判断
根据 与运算 定义,设二进制数字 n ,则有:
算法流程:
代码:
public class Solution {
public int hammingWeight(int n) {
int res = 0;
while(n != 0) {
res += n & 1;
n >>>= 1;
}
return res;
}
}
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
示例:
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
动态规划:
class Solution {
public int numTrees(int n) {
int[] dp = new int[n+1];//dp[n]代表节点数为n所能构成的二叉搜索树的数目
dp[0] = 1; dp[1] = 1;
for (int i=2; i<=n; i++) {
for (int j=1; j<=i; j++) {
dp[i] += dp[j-1] * dp[i-j]; // 以j作为根节点
}
}
return dp[n];
}
}
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
示例 1:
输入:word1 = “horse”, word2 = “ros”
输出:3
解释:
horse -> rorse (将 ‘h’ 替换为’r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)
示例 2:
输入:word1 = “intention”, word2 = “execution”
输出:5
解释:
intention ->inention (删除 ‘t’)
inention -> enention (将 ‘i’ 替换为 ‘e’)
enention ->exention (将 ‘n’ 替换为 ‘x’)
exention -> exection (将 ‘n’ 替换为 ‘c’)
exection -> execution (插入 ‘u’)
提示:
解题思路:
代码
class Solution {
public int minDistance(String word1, String word2) {
int m = word1.length();
int n = word2.length();
int[][] dp = new int[m+1][n+1];
// 计算边界值第一行
for (int i = 1; i <= n; i++) dp[0][i] = dp[0][i - 1] + 1;
// 计算边界值第一列
for (int i = 1; i <= m; i++) dp[i][0] = dp[i - 1][0] + 1;
// 计算其他位置
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;
}
}
return dp[m][n];
}
}
假设你正在爬楼梯,需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
示例 2:
输入: 3 输出: 3 解释: 有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 2 阶 + 1 阶
动态规划:
f(x)=f(x−1)+f(x−2)
class Solution {
public int climbStairs(int n) {
int pre = 1, cur = 1;
for (int i=2; i<=n; i++) {
int temp = pre + cur;
pre = cur;
cur = temp;
}
return cur;
}
}
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
示例 2:
输入:grid = [[1,2,3],[4,5,6]]
输出:12
提示:
动态规划:
class Solution {
public int minPathSum(int[][] grid) {
int[][] dp = new int[grid.length][grid[0].length];
dp[0][0] = grid[0][0];
for (int i=1; i<grid.length; i++) {
dp[i][0] = grid[i][0] + dp[i-1][0];
}
for (int i = 1; i<grid[0].length; i++) {
dp[0][i] = grid[0][i] + dp[0][i-1];
}
for (int i = 1; i<grid.length; i++) {
for (int j=1; j<grid[0].length; j++) {
dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
}
}
return dp[grid.length-1][grid[0].length-1];
}
}
给定一个非负整数数组 nums ,你最初位于数组的第一个下标。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
提示:
动态规划:
class Solution {
public boolean canJump(int[] nums) {
int righMost = 0;
for (int i=0; i<nums.length; i++) {
if (i <= righMost) {
righMost = Math.max(righMost, i+nums[i]);
if (righMost >= nums.length-1) {
return true;
}
} else {
return false;
}
}
return false;
}
}
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [0]
输出:0
示例 4:
输入:nums = [-1]
输出:-1
示例 5:
输入:nums = [-100000]
输出:-100000
提示:
动态规划:
class Solution {
public int maxSubArray(int[] nums) {
int pre = 0, maxAns = Integer.MIN_VALUE;
for (int x : nums) {
pre = Math.max(pre + x, x);
maxAns = Math.max(maxAns, pre);
}
return maxAns;
}
}
给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = “(()”
输出:2
解释:最长有效括号子串是 “()”
示例 2:
输入:s = “)()())”
输出:4
解释:最长有效括号子串是 “()()”
示例 3:
输入:s = “”
输出:0
提示:
0 <= s.length <= 3 * 104
s[i] 为 ‘(’ 或 ‘)’
动态规划:定义dp[i] 表示以下标 i 字符结尾的最长有效括号的长度.
class Solution {
public int longestValidParentheses(String s) {
int len = s.length();
int[] dp = new int[len];
int max = 0;
for (int i = 1; i < len; i++) {
if (s.charAt(i) == ')') {
if (s.charAt(i-1) == '(') {
dp[i] = i < 2 ? 2 : dp[i-2] + 2;
} else if (i - dp[i-1] - 1 >= 0 && s.charAt(i - dp[i -1] - 1) == '(') {
dp[i] = 2 + dp[i - 1] + ((i - dp[i - 1] - 2) >= 0 ? dp[i - dp[i - 1] - 2] : 0);
}
max = Math.max(max, dp[i]);
}
}
return max;
}
}
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
示例 1:
提示:
动态规划:
class Solution {
public int maxValue(int[][] grid) {
int[][] dp = new int[grid.length][grid[0].length];
dp[0][0] = grid[0][0];
for (int i=1; i<grid.length; i++) {
dp[i][0] = grid[i][0] + dp[i-1][0];
}
for (int i=1; i<grid[0].length; i++) {
dp[0][i] = grid[0][i] + dp[0][i-1];
}
for (int i=1; i<grid.length; i++) {
for (int j=1; j<grid[0].length; j++) {
dp[i][j] = Math.max(dp[i-1][j] ,dp[i][j-1]) + grid[i][j];
}
}
return dp[dp.length-1][dp[0].length-1];
}
}
给定两个字符串str1和str2,输出两个字符串的最长公共子串。
题目保证str1和str2的最长公共子串存在且唯一。
示例1
输入
“1AB2345CD”,“12345EF”
返回值
“2345”
备注:
1 <= len(str1),len(str2) <= 5000
import java.util.*;
class Solution {
/**
* longest common substring
* @param str1 string字符串 the string
* @param str2 string字符串 the string
* @return string字符串
*/
public String LCS(String str1, String str2) {
int m = str1.length();
int n = str2.length();
// dp[i][j] str1前i个字符和str2前j个字符(子字符串以str1[i-1]和str2[j-1]结尾)的最长公共子串长度
int[][] dp = new int[m+1][n+1];
int maxLen = 0, end = 0;
//开始填表
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= n; j++) {
if(str1.charAt(i-1) == str2.charAt(j-1)) dp[i][j] = dp[i-1][j-1] + 1;
else dp[i][j] = 0;
if(dp[i][j] > maxLen) {
maxLen = dp[i][j];
end = i - 1;
}
}
}
return str1.substring(end-maxLen+1, end+1);
}
}
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
限制:
0 <= 数组长度 <= 10^5
动态规划:
class Solution {
public int maxProfit(int[] prices) {
int cost = Integer.MAX_VALUE, profit = 0;
for (int price : prices) {
cost = Math.min(cost, price);
profit = Math.max(profit, price - cost);
}
return profit;
}
}
给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B 中的元素 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。
示例:
输入: [1,2,3,4,5]
输出: [120,60,40,30,24]
提示:
class Solution {
public int[] constructArr(int[] a) {
if(a.length == 0) return new int[0];
int[] b = new int[a.length];
//正三角
b[0] = 1;
for(int i = 1; i < a.length; i++) {
b[i] = b[i - 1] * a[i - 1];
}
//倒三角
int tmp = 1;
for(int i = a.length - 2; i >= 0; i--) {
tmp *= a[i + 1];
b[i] *= tmp;
}
return b;
}
}
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", “bwfi”, “bczi”, “mcfi"和"mzi”
提示:
0 <= num < 2^31
动态规划:
class Solution {
public int translateNum(int num) {
int curRes = 1, preRes = 1, preDigit = num % 10;
while (num != 0) {
num /= 10;
int curDigit = num % 10;
int judge = 10 * curDigit + preDigit;
int res = (judge >= 10 && judge <= 25) ? curRes + preRes : curRes;
preRes = curRes;
curRes = res;
preDigit = curDigit;
}
return curRes;
}
}
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
提示:
s.length <= 40000
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();
int res = 0, tmp = 0;
for(int right = 0; right < s.length(); right++) {
int left = dic.getOrDefault(s.charAt(right), -1); // 获取索引left
dic.put(s.charAt(right), right); // 更新哈希表
tmp = tmp < right - left ? tmp + 1 : right - left; // dp[right-1] -> dp[right]
res = Math.max(res,tmp);
}
return res;
}
}
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
class Solution {
public int trap(int[] height) {
if (height==null || height.length==0) {
return 0;
}
int ans = 0;
int size = height.length;
int[] leftMax = new int[size];
int[] rightMax = new int[size];
leftMax[0] = height[0];
for (int i=1; i<size; i++) {
leftMax[i] = Math.max(height[i], leftMax[i-1]);
}
rightMax[size-1] = height[size-1];
for (int i=size-2; i>=0; i--) {
rightMax[i] = Math.max(height[i], rightMax[i+1]);
}
for (int i=1; i<size-1; i++) {
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
}
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:1
示例 2:
输入:n = 5
输出:5
class Solution {
public int fib(int n) {
if(n == 0) return 0;
if(n == 1) return 1;
int pre = 0, cur = 1;
for (int i = 2; i <= n; i++) {
int temp = (pre + cur) % 1000000007;
pre = cur;
cur = temp;
}
return cur;
}
}
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21
示例 3:
输入:n = 0
输出:1
解题思路无限接近斐波那契数列,唯一不同是初始值不一样。
当n >= 2时:
n级有两类跳法:n-1级跳1级,以及n-2级跳2级;
因此n级跳法数 = n-1级跳法数 + n-2级跳法数。
class Solution {
public int numWays(int n) {
if (n==0 || n==1) return 1;
int pre = 1, cur = 1;
for (int i = 2; i <= n; i++) {
int temp = (pre + cur) % 1000000007;
pre = cur;
cur = temp;
}
return cur;
}
}
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 58
class Solution {
public int cuttingRope(int n) {
int[] dp = new int[n+1];
for (int i=2; i<=n; i++) {
int curMax = 0;
for (int j=1; j<i; j++) {
curMax = Math.max(curMax, Math.max(j * (i - j), j * dp[i - j]));
}
dp[i] = curMax;
}
return dp[n];
}
}
算法流程:
class Solution {
public int cuttingRope(int n) {
if(n <= 3) return n - 1;
int a = n / 3, b = n % 3;
if(b == 0) return (int)Math.pow(3, a);
if(b == 1) return (int)Math.pow(3, a - 1) * 4;
return (int)Math.pow(3, a) * 2;
}
}
给你一个整数 n,请返回长度为 n 、仅由元音 (a, e, i, o, u) 组成且按 字典序排列 的字符串数量。
字符串 s 按 字典序排列 需要满足:对于所有有效的 i,s[i] 在字母表中的位置总是与 s[i+1] 相同或在 s[i+1] 之前。
示例 1:
输入:n = 1
输出:5
解释:仅由元音组成的 5 个字典序字符串为 [“a”,“e”,“i”,“o”,“u”]
示例 2:
输入:n = 2
输出:15
解释:仅由元音组成的 15 个字典序字符串为[“aa”,“ae”,“ai”,“ao”,“au”,“ee”,“ei”,“eo”,“eu”,“ii”,“io”,“iu”,“oo”,“ou”,“uu”]
注意,“ea” 不是符合题意的字符串,因为 ‘e’ 在字母表中的位置比 ‘a’ 靠后
示例 3:
输入:n = 33
输出:66045
提示:
1 <= n <= 50
动态规划:定义dp[n+1][5]数组,其中dp[i][0-4]表示长度为i的以a-u结尾的字符串的个数。
class Solution {
public int countVowelStrings(int n) {
int[][] dp = new int[n+1][5];
//初始化n=1的情况
for (int i = 0; i < 5; i++){
dp[1][i] = 1;
}
for (int i = 2; i <= n; i++){
dp[i][0] = dp[i-1][0];
dp[i][1] = dp[i-1][0] + dp[i-1][1];
dp[i][2] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2];
dp[i][3] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2] + dp[i-1][3];
dp[i][4] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2] + dp[i-1][3] + dp[i-1][4];
}
//最终答案求和
return dp[n][0] + dp[n][1] + dp[n][2] + dp[n][3] + dp[n][4];
}
}
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
动态规划:
设已知长度为n的丑数序列x1,x2,…xn,求第n+1个丑数xn+1。根据递推性质,丑数xn+1只可能是以下三种情况之一(索引a,b,c为未知数):
由于xn+1是最接近xn的丑数,因此索引a,b,c需满足以下条件:
因此,可设置指针 a,b,c 指向首个丑数,循环根据递推公式得到下个丑数,并每轮将对应指针执行 +1 即可。
class Solution {
public int nthUglyNumber(int n) {
int preIndex2 = 0, preIndex3 = 0, preIndex5 = 0;
int[] dp = new int[n];
dp[0] = 1;
for (int i=1; i<n; i++) {
int res2 = dp[preIndex2] * 2, res3 = dp[preIndex3] * 3, res5 = dp[preIndex5] * 5;
dp[i] = Math.min(Math.min(res2, res3), res5);
if (dp[i]==res2) preIndex2++;
if (dp[i]==res3) preIndex3++;
if (dp[i]==res5) preIndex5++;
}
return dp[n-1];
}
}
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
示例 1:
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]
限制:
1 <= n <= 11
class Solution {
public double[] dicesProbability(int n) {
//根据动态规划的思想分解子问题
//把n个骰子的点数分解为n-1个骰子的点数加上一个骰子的点数
double[] pre = {1/6d, 1/6d, 1/6d, 1/6d, 1/6d, 1/6d};
for(int i = 2; i <= n; i++) {
double[] temp = new double[5*i+1];
for (int j = 0; j < pre.length; j++) {
for(int x = 0; x < 6; x++) {
//构造状态转移方程:tmp[x+y]+=pre[x]*num[y],num[y]在本题恒等于1/6
temp[j+x] += pre[j] / 6;
}
}
pre = temp;
}
return pre;
}
}
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad" 输出: "bab" 注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
"回文串”是一个正读和反读都一样的字符串
1 定义状态
dp[i][j] 表示子串 s[i…j] 是否为回文子串,这里子串 s[i…j] 定义为左闭右闭区间,可以取到 s[i] 和 s[j];
2 状态转移方程
dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1];
3 边界条件
表达式 [i + 1, j - 1] 不构成区间,即长度严格小于 2,即 j - 1 - (i + 1) + 1 < 2 ,整理得 j - i < 3;
4 输出
只要得到 dp[i][j] = true且长度大于之前得到的最大长度,就记录子串的长度和起始位置,没有必要截取,这是因为截取字符串也要消耗性能,记录此时的回文子串的「起始位置」和「回文长度」即可;
public class Solution {
public String longestPalindrome(String s) {
int n = s.length();
int maxLen = 1;
int begin = 0;
// dp[i][j]表示s[i..j]是否是回文串,左闭右闭
boolean[][] dp = new boolean[n][n];
for (int j=1; j<n; j++) {
for (int i=0; i<j; i++) {
if (s.charAt(i) == s.charAt(j) && (j-i < 3 || dp[i+1][j-1])) {
dp[i][j] = true;
if (j-i+1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
}
return s.substring(begin, begin + maxLen);
}
}
复杂性分析
时间复杂度为 O(N2),空间复杂度为 O(N2)。
两种情况:
public class Solution {
public String longestPalindrome(String s) {
int n = s.length();
int maxLen = 1;
int begin = 0;
for (int i = 0; i < n * 2 - 1; i++) {
int left = i / 2;
int right = (i + 1) / 2;
while (left >= 0 && right < n && s.charAt(left) == s.charAt(right)) {
if (right - left + 1 > maxLen) {
maxLen = right - left + 1;
begin = left;
}
left--;
right++;
}
}
return s.substring(begin, begin + maxLen);
}
}
复杂性分析
时间复杂度为 O(N2),空间复杂度为 O(1)。
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖整个字符串s的,而不是部分字符串。
示例 1:
输入:s = "aa" p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:s = "aa" p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:
输入:s = "ab" p = ".*"
输出:true
解释:".*" 表示可匹配零个或多个('*')任意字符('.')。
示例 4:
输入:s = "aab" p = "c*a*b"
输出:true
解释:因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:
输入:s = "mississippi" p = "mis*is*p*."
输出:false
提示:
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length(), n = p.length();
boolean[][] dp = new boolean[m+1][n+1];
// 空匹配空
dp[0][0] = true;
// 初始化首行
for (int i=2; i<=n; i+=2) {
if (p.charAt(i-1) == '*') dp[0][i] = dp[0][i-2]; // a*代表0个a
}
// 状态转移:一步步填充表中其他的空格,dp[i][j]表示s的前i个字符与p的前j个字符是否能够匹配
for (int i=1; i<=m; i++) {
for (int j=1; j<=n; j++) {
if (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '.') {// 题目保证每次出现字符*时,前面都匹配到有效的字符,因此不会出现j-2<0的越界情况
dp[i][j] = dp[i-1][j-1];
} else if (p.charAt(j-1) == '*') {
if (p.charAt(j-2) == s.charAt(i-1) || p.charAt(j-2) == '.') {
dp[i][j] |= dp[i][j-2]; // a*代表0个a
dp[i][j] |= dp[i][j-1]; // a*代表1个a
dp[i][j] |= dp[i-1][j-1];// a*代表2个a
dp[i][j] |= dp[i-1][j]; // a*代表大于2个a
} else {
dp[i][j] = dp[i][j-2]; // a*代表0个a
}
}
}
}
return dp[m][n];
}
}
每次只允许不大于两人通过,他们只有一个手电筒,所以每次过桥的两个人需要把手电筒带回来,i号小朋友过桥的时间为T[i],两个人过桥的总时间为二者中时间长者。问所有小朋友过桥的总时间最短是多少?
import java.util.Arrays;
import java.util.Scanner;
public class CrossRiver {
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
int[] timeCost = new int[num];
for(int i = 0; i < num; i++)
{
timeCost[i] = sc.nextInt();
}
Arrays.sort(timeCost);
if (num < 3) {
System.out.println(timeCost[num - 1]);
} else if (num == 3) {
System.out.println(timeCost[1] + timeCost[0] + timeCost[2]);
} else {
int[] dp = new int[num];
dp[0] = timeCost[0];
dp[1] = timeCost[1];
dp[2] = timeCost[1] + timeCost[0] + timeCost[2];
for(int i = 3; i < num; i++)
{
dp[i] = Math.min(dp[i-1] + timeCost[0] + timeCost[i], dp[i-2] + timeCost[0] + 2 * timeCost[1] + timeCost[i]);
}
System.out.println(dp[num - 1]);
}
}
}