字符串
多数元素
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
我的求解:
Java
class Solution {
public int majorityElement(int[] nums) {
// 构建一个辅助map
// 如果数组小于等于2,又假定一定有多数元素,则直接返回第一个元素即可
if(nums.length == 0){
return -1;//这里应该抛出错误,-1假定都为正
}
if(nums.length <= 2){
return nums[0];
}
Map<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int i = 0; i < nums.length; i++){
if(map.containsKey(nums[i])){
map.put(nums[i],map.get(nums[i])+1);
}else{
map.put(nums[i],1);
}
}
// 遍历map,找到多数元素
int maxKey = 0;
int maxValue = 0;
for(Map.Entry<Integer,Integer> entry : map.entrySet()){
if(entry.getValue() > maxValue){
maxKey = entry.getKey();
maxValue = entry.getValue();
}
}
return maxKey;
}
}
中规中矩,时间复杂度o(n),空间复杂度o(n)
但是还有更强大的摩尔投票算法可以达到o(1)的空间复杂度
Java
class Solution {
public int majorityElement(int[] nums) {
// 莫尔投票算法
// 或称之为六大门派围攻光明顶
// 明教的势力大于所有的势力
// 按照如下规则:
// 车轮战依次对决
// 遇到自己教的人,生命值+1
// 遇到其他教的人,同归于尽,生命值-1
// 若当前被挑战者死了,下一个上去的人成为挑战者
// 由于明教太强了,最后留下来的一定是明教的人
if(nums.length == 0){
return -1;
}
int master = nums[0];
int masterHP = 1;
for (int i= 1; i < nums.length; i++){
if(masterHP == 0){
master = nums[i];
masterHP = 1;
continue;
}
if(master == nums[i]){
masterHP+=1;
}else{
masterHP -=1;
}
}
return master;
}
}
轮转数组
问题描述
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
我的解答:
Java
class Solution {
public void rotate(int[] nums, int k) {
// 构建一个辅助数组即可 -> 空间复杂度o(n)
int[] result = new int[nums.length];
for(int i = 0; i < nums.length; i++){
result[(i+k)%nums.length] = nums[i];
}
System.arraycopy(result,0,nums,0,nums.length);
}
}
官方解答:
Java
class Solution {
public void rotate(int[] nums, int k) {
// 使用再反转法
// 将数组分为两部分:n-k,k
// 最终我们想得到:k,n-k这样的结果
// 1. 先将数组整体进行翻转,得到 Rk,Rn-k
// 2. 然后将Rk和Rn-k分别反转
// 即R(Rk) = k,R(Rn-k) = n-k,由此得到了k,n-k
k %= nums.length;
reverse(nums,0,nums.length-1);
reverse(nums,0,k-1);
reverse(nums,k,nums.length-1);
}
private void reverse(int[] nums, int start, int end){
while(start < end){
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
}
买卖股票的最佳时机
题目描述:
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
我的代码:
Java
class Solution {
public int maxProfit(int[] prices) {
// 在股票最低的时候买入,然后在买入后最高点卖出
int minIndex = 0;
for(int i = 0; i < prices.length; i++){
if(prices[i] < prices[minIndex]){
minIndex = i;
}
}
// 从买入的数组找到最大值
int maxIndex = minIndex;
for (int i = minIndex; i < prices.length; i++){
if(prices[maxIndex] < prices[i]){
maxIndex = i;
}
}
return prices[maxIndex] - prices[minIndex];
}
}
存在问题,例如对于[4,2,1]这样的输入,不能得到正确答案,实际上不应该维护一个价格全局最低点,而是应该维护一个当前能看到的全局价格最低点
买卖股票的最佳时机 II
题目描述
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。然而,你可以在 同一天 多次买卖该股票,但要确保你持有的股票不超过一股。
返回 你能获得的 最大 利润 。
我的解答:
Java
class Solution {
public int maxProfit(int[] prices) {
// 吃每个谷 - 峰的利润
int allProfit = 0;
for(int i = 0; i < prices.length -1; i++){
if(prices[i] < prices[i+1]){
// 如果下一天为增,则加上下一天的收益
allProfit += prices[i+1] - prices[i];
}
}
return allProfit;
}
}
贪心最优,没问题
跳跃游戏
题目描述:
给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
我的解答:
Java
class Solution {
public boolean canJump(int[] nums) {
// 维护一个辅助数组记录能到达的位置
boolean[] canGo = new boolean[nums.length];
Arrays.fill(canGo,false);
canGo[0] = true;
for(int i = 0; i < nums.length; i++){
if(canGo[i]){
// 原来是这样写的,最多走n步我就只走了n步,这不对
// canGo[Math.min(i+nums[i],nums.length-1)] = true;
// 需要注意,fill是[startIndex,endIndex)
Arrays.fill(canGo,i,Math.min(i+nums[i]+1,nums.length),true);
}
}
return canGo[nums.length-1];
}
}
看了思路后我的解答
Java
class Solution {
public boolean canJump(int[] nums) {
// 遍历数组,记录能到达的最大的位置
int maxLocation = 0;
for(int i = 0; i < nums.length; i++){
// 还是有问题,应该先看能不能到这里,再更新
// 对于[3,2,1,0,4],会错误的得到true
if(nums[i]+ i > maxLocation){
maxLocation = nums[i] + i;
}
if(i > maxLocation){
return false;
}
}
return maxLocation >= nums.length ? true : false;
}
}
跳跃游戏 II
题目描述:
给定一个长度为 n 的 0 索引整数数组 nums。初始位置在下标 0。
每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在索引 i 处,你可以跳转到任意 (i + j) 处:
0 <= j <= nums[i] 且
i + j < n
返回到达 n - 1 的最小跳跃次数。测试用例保证可以到达 n - 1。
示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
我的解答:
Java
class Solution {
public int jump(int[] nums) {
// dp[i] 表示跳跃到i需要的最小步数
// dp[0] = 0
// 对nums进行遍历:
// 对于j <= i+nums[i]且j<nums.length
// dp[j] = min(dp[j],dp[i]+1);
int[] dp = new int[nums.length];
Arrays.fill(dp,Integer.MAX_VALUE);
dp[0] = 0;
for(int i = 0; i < nums.length; i++){
for(int j = i+1 ; j < nums.length && j <= i + nums[i]; j++){
dp[j] = Math.min(dp[j],dp[i]+1);
}
}
return dp[nums.length-1];
}
}
采用动态规划算法,时间复杂度o(n^2)
官方解答:
Java
class Solution {
public int jump(int[] nums) {
// 采用贪心算法,
// 现在在第i个位置,从i能跳到第j个位置
// 不是直接跳到第j个位置,而是遍历i到j
// 找到k属于(i,j],而k+nums[i]最大
// 即从最有潜力的位置跳
int nextJump = 0; // 下一次能跳到的最远的位置
int currentJump = 0; // 当前能跳到的最远位置
int jumpCount = 0;// 跳了多少次
// 由于题目确保必定可以到达n - 1
// 则在n-1的时候并不需要建桥,故只到n-2即可
// 遍历到n-2要么已经可以到n-1了,要么构建一个从n-2到n-1的桥
for(int i = 0; i < nums.length-1; i++){
if(i+nums[i] > nextJump){
nextJump = i+nums[i];
}
if(i == currentJump){
// 当前已经走到了能到的边界,则遍历完了需要跳了
jumpCount++;
currentJump = nextJump;
}
}
return jumpCount;
}
}
H 指数
题目描述
给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。
根据维基百科上 h 指数的定义:h 代表"高引用次数" ,一名科研人员的 h 指数 是指他(她)至少发表了 h 篇论文,并且 至少 有 h 篇论文被引用次数大于等于 h 。如果 h 有多种可能的值,h 指数 是其中最大的那个。
示例 1:
输入:citations = [3,0,6,1,5]
输出:3
解释:给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 3, 0, 6, 1, 5 次。
由于研究者有 3 篇论文每篇 至少 被引用了 3 次,其余两篇论文每篇被引用 不多于 3 次,所以她的 h 指数是 3。
我的解答:
Java
class Solution {
public int hIndex(int[] citations) {
int sum = Arrays.stream(citations).sum();
// h小于等于sum值的1/2次方
int h = (int)Math.sqrt(sum) + 1;
// h小于数组length
h = Math.min(h,citations.length);
for(; h >=0; h--){
int hCount = 0;
for(int j = 0; j < citations.length; j++){
if(citations[j] >= h){
hCount++;
}
}
if(hCount >= h){
return h;
}
}
return 0;
}
}
虽然我的方法控制了h的初值不至于太大,但是仍旧是o(n^2)的算法
核心优化为:先排序 ,这样就不用每次都进行遍历去数hCount了
排序之后从后往前进行查找,如果第i个大于h,那么第i到第n个都一定大于h
则h不断增大,i不断减小,,则此时为最大h。
因为h增大了多少次,i就减小了多少次,也就是说如果遇到了某个citations[i] 大于h,也其后面的数也肯定大于h
Java
class Solution {
public int hIndex(int[] citations) {
Arrays.sort(citations);
int h = 0;
int i = citations.length -1;
// 这里不能写citations[i] >= h
// 因为如果现在是citations[i] == h
// 则h已经是最大了,不能再增大了
// 对于h+1,不能满足citations[i] > h+1
while(i >=0 && citations[i] > h){
h++;
i--;
}
return h;
}
}
由于排序导致时间复杂度为o(nlgn),而这里由于h不会超过citations的长度,则可以进行基数排序
Java
class Solution {
public int hIndex(int[] citations) {
// 进行基数排序
int arrayLength = citations.length;
int[] sortedCitations = new int[arrayLength+1];
Arrays.fill(sortedCitations,0);
for(int i = 0; i < arrayLength; i++){
sortedCitations[Math.min(citations[i],arrayLength)] += 1;
}
// 现在sortedCitations[i] = j表示数字i有j个
// 记录当前遍历到的数字后面有多少数字
int afterCount = 0;
int i = arrayLength;
while(i >= 0 && sortedCitations[i] + afterCount < i){
// 当前i不能满足
afterCount += sortedCitations[i];
i--;
// 我最开始的代码这里犯了一个重大错误:
// 我原来是这样写的:
// i--;
// afterCount += sortedCitations[i];
}
return i;
}
}
O(1) 时间插入、删除和获取随机元素
实现RandomizedSet 类:
RandomizedSet() 初始化 RandomizedSet 对象
bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。
bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false 。
int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。
你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1) 。
做不来,直接看官方题解:
Java
class RandomizedSet {
//变长数组 + 哈希表
// 存储数据
List<Integer> nums;
// 存储每个元素在nums中的下标
Map<Integer,Integer> indices;
Random random;
public RandomizedSet() {
nums = new ArrayList<Integer>();
indices = new HashMap<Integer,Integer>();
random = new Random();
}
public boolean insert(int val) {
if(indices.containsKey(val)){
return false;
}
int index = nums.size();
nums.add(val);
indices.put(val,index);
return true;
}
public boolean remove(int val) {
if(!indices.containsKey(val)){
return false;
}
int index = indices.get(val);
// 将最后一个元素移动到remove的位置
int last = nums.get(nums.size() -1);
nums.set(index,last);
indices.put(last,index);
// 执行元素删除
nums.remove(nums.size()-1);
indices.remove(val);
return true;
}
public int getRandom() {
int randomIndex = random.nextInt(nums.size());
return nums.get(randomIndex);
}
}
对hashmap的初探
hashmap实际上是数组+链表+红黑树
- 使用数组来进行hash桶的模拟
- 如果产生碰撞,则用链表连起来
- 如果链表过长,则转化为红黑树。这确保了在极端哈希碰撞情况下,也会得到n(lgn)的时间复杂度
一些实现细节:
- 哈希值的生成
不直接对hashCode进行取余,而是整合了高低位的信息
Java
static final int hash(Object key) {
int h;
// 无符号右移16位,即得到高位信息
// 然后将高位信息和低位信息进行异或操作
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 索引值的生成
使用位运算"交"来替换取余操作(n - 1) & hash
->需要确保n的值为2的整数幂,这样n-1就全是11111
否则会造成某些位用不了
除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请** 不要使用除法**,且在 O(n) 时间复杂度内完成此题。
示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
我的解答:
Java
class Solution {
public int[] productExceptSelf(int[] nums) {
// 构造两个辅助数组
// preMul[i]代表[0,i)的元素乘起来是多少
// nextMul[i]代表[i,n)的元素乘起来是多少
// 然后将这两个数组里面对应元素相乘即可
int arrayLength = nums.length;
int[] preMul = new int[arrayLength];
int[] nextMul = new int[arrayLength];
// 从前往后逐渐累积,记录前缀乘积
// 初始化第一个元素
preMul[0] = 1;
for(int i = 1; i < arrayLength; i++){
preMul[i] = preMul[i-1] * nums[i-1];
}
// 从后往前,记录后缀乘积
// 同理,最后一个元素也应该初始化为1
nextMul[arrayLength-1] = 1;
for(int i = arrayLength - 2; i >= 0; i--){
nextMul[i] = nextMul[i+1] * nums[i+1];
}
// 相乘得到最终结果
int[] result = new int[arrayLength];
for(int i = 0; i < arrayLength; i++){
result[i] = preMul[i] * nextMul[i];
}
return result;
}
}
还可以进一步优化
Java
class Solution {
public int[] productExceptSelf(int[] nums) {
int arrayLength = nums.length;
int[] result = new int[arrayLength];
int accumulator = 1; // 累加变量
//初始化头尾
result[0] = 1;
result[arrayLength-1]=1;
for(int i = 1; i < arrayLength; i++){
result[i] = accumulator * nums[i-1];
accumulator*=nums[i-1];
}
// 从后往前,重置累加变量
accumulator = 1;
for(int i = arrayLength - 2; i >= 0; i--){
result[i] *= nums[i+1] * accumulator;
accumulator*=nums[i+1];
}
return result;
}
}
加油站
题目描述:
在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。
示例 1:
输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。
我的解答:
Java
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
// 暂时只想到o(n^2)的算法
// 将value[i]计算为gas[i] - cost[i]
// value[i]的意思为到节点i值不值
// 例如value[i] = 2,表示到这里会多两升油,值
// value[i] = -3,表示到这里会少三升油,不值
// 第一次肯定要从非负的开始,然后依次遍历
// 保证有解我就不写异常值判断了
int arrayLength = gas.length;
int[] value = new int[arrayLength];
for(int i = 0; i < arrayLength; i++){
value[i] = gas[i] - cost[i];
}
for(int i = 0; i < arrayLength; i++){
if(value[i] >= 0){
int acc = value[i];//累加器,已经走第一步了,加上i的价值
int j = (i+1)%arrayLength;//循环走
for(; j != i; j = (j+1)%arrayLength){
acc += value[j];
if(acc <=0 ){
break;
}
}
// 前者表示走回来了还有油
// 后者表示走回来刚好没油
if((j ==i) || ((j+1)%arrayLength == i && acc == 0)){
return i;
}
}
}
return -1;
}
}
官方解答:
Java
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
// "已经在谷底了,怎么走都是向上"
// 首先判断能不能走回去
// 如果gas的总和小于cost的总和,那么一定走不回去
// 否则一定可以找到某种路径
// value的定义不变,但是从最小value的下一个开始即可
// 核心在于集中所有力量,克服最大的困难
// 如果能回去,最大困难也是大于等于0的
// 那么集中力量的过程中也不会中道崩殂
int acc = 0; //累加和
int minIndex = 0;
int minAcc = Integer.MAX_VALUE;
for(int i = 0 ; i < gas.length; i++){
acc += gas[i] - cost[i];
if(acc < minAcc){
minAcc = acc;
minIndex = i;
}
}
if(acc >= 0){
return (minIndex+1)%gas.length;
}else{
return -1;
}
}
}
分发糖果
题目描述:
你需要按照以下要求,给这些孩子分发糖果:
每个孩子至少分配到 1 个糖果。
相邻两个孩子中,评分更高的那个会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
我最开始的做法
Java
class Solution {
public int candy(int[] ratings) {
// 数组是由无数段 连增、连减 构成的
// 对于连增段,例如从i到j为增
// 则i要1个糖果,i+1要2个糖果,一直到j要j-i+1个糖果
// 对于连减段,例如从p到q为减
// 则q要q-p+1个糖果,p+1要2个糖果,一直到p要1个糖果
// 对于平滑段,糖果数相同即可
// 计算趋势
int arrayLength = ratings.length;
// 如果只有一个孩子,直接返回1
if(arrayLength == 1){
return 1;
}
// 不包括最后一个元素
int[] r = new int[arrayLength-1];
for(int i = 0; i < arrayLength-1; i ++){
r[i] = ratings[i+1] - ratings[i];
}
// 计算初始趋势
int trend = 0;
if(r[0] > 0){
trend = 1;
}
if(r[0] > 0){
trend =-1;
}
// 初始化糖果
int candyCount = 1;
// 总计需要的糖果
int acc = 0;
for(int i = 0; i < arrayLength -1; i++){
if(r[i] == 0){
// 进入"平"阶段
trend = 0;
acc += candyCount;
}
if(r[i] > 0){
if(trend == 1){
// 持续上升
acc += candyCount;
candyCount++;
}else if(trend == -1){
// 谷底
acc += candyCount;
candyCount++;
trend = 1;
}else{
// trend == 0
// "平"到增
acc += candyCount;
candyCount++;
trend = 1;
}
}
if(r[i] < 0){
if(trend == 1){
// 峰
acc += candyCount;
candyCount--;
trend = -1;
}else if(trend == -1){
// 持续下降
acc += candyCount;
candyCount--;
}else{
// trend == 0
// 平到减
acc += candyCount;
candyCount --;
trend = -1;
}
}
}
// 处理最后
acc += candyCount;
return acc;
}
}
存在问题的,没有全局视野,陷入局部矛盾。如果先是一个小谷底,再来一个大谷底,那么小谷底那里为1,大谷底就只能是负的了
借鉴加油站的思路?
Java
class Solution {
public int candy(int[] ratings) {
// 求全局最小点然后赋为1,接着从左至右再遍历一次
// 遇到增则candyCount值加一,减则candyCount值减一
// 考虑可能会存在多个全局最小点,则记录全局最小的值
// 如果又遇到了全局最小值,则给他一个糖果,candyCount值赋为1,继续向右遍历即可
int arrayLength = ratings.length;
int minValue = Integer.MAX_VALUE;
int minIndex = 0;
for(int i = 0; i < arrayLength; i++){
if(minValue > ratings[i]){
minValue = ratings[i];
minIndex = i;
}
}
int acc = 0;
int candyCount = 1;
// 从找到的一个全局最小点往左边
for(int i = minIndex; i > 0; i--){
acc += candyCount;
// 如果又遇到了全局最小值则右边的约束满足了,
// 左边从1开始也可以满足约束
// 故,重置candyCount为1;
if(ratings[i-1] == minValue){
candyCount = 1;
}
if(ratings[i-1] > ratings[i]){
candyCount++;
}else if(ratings[i-1] < ratings[i]){
candyCount--;
} // 如果相等则不变
}
acc += candyCount; // 加上最左边的值
// 从找到的一个全局最小点往右边
// 这里超级容易错,不能i = minIndex,否则多算了依次这个全局最低点
// 其次因为从minIndex+1开始,candyCount就应该为2而不是1了
// 不对,还是需要进行初值判断,不应该为2,有可能下一个也是最小值
if(minIndex < arrayLength-1){
// 如果有下一个
candyCount = ratings[minIndex+1] == minValue ? 1: 2;
}else{
candyCount = 1;
}
for(int i = minIndex+1; i < arrayLength-1; i++){
acc += candyCount;
// 如果又遇到了全局最小值则左边的约束满足了,
// 右边从1开始也可以满足约束
// 故,重置candyCount为1;
if(ratings[i+1] == minValue){
candyCount = 1;
}
if(ratings[i+1] > ratings[i]){
candyCount++;
}else if(ratings[i+1] < ratings[i]){
candyCount--;
} // 如果相等则不变
}
acc += candyCount; // 加上最右边的值
return acc;
}
}
还是存在问题,无法处理水平段,例如对于[1,2,2]会得到6([1,2,2])而非5([1,2,1])
官方解答:
Java
class Solution {
public int candy(int[] ratings) {
// 这其实是局部约束,不应该像加油一样使用全局来进行处理
// 实际上就是两个规则
// 左规则:如果大于左边,则比左边的糖多
// 右规则:如果大于右边,则比右边的糖多
// 然后取最大的值即可
int arrayLength = ratings.length;
int[] left = new int[arrayLength];
int[] right = new int[arrayLength];
Arrays.fill(left,1);
Arrays.fill(right,1);
for(int i = 1; i < arrayLength; i++){
if(ratings[i-1] < ratings[i]){
left[i] = left[i-1]+1;
}
}
// 右规则
for(int i = arrayLength -2 ; i >= 0; i--){
if(ratings[i] > ratings[i+1]){
right[i] = right[i+1]+1;
}
}
// 合并
int acc = 0;
for(int i = 0; i < arrayLength; i++){
acc += Math.max(left[i],right[i]);
}
return acc;
}
}
还可以就使用一个数组:
Java
class Solution {
public int candy(int[] ratings) {
// 这其实是局部约束,不应该像加油一样使用全局来进行处理
// 实际上就是两个规则
// 左规则:如果大于左边,则比左边的糖多
// 右规则:如果大于右边,则比右边的糖多
// 然后取最大的值即可
int arrayLength = ratings.length;
int[] result = new int[arrayLength];
Arrays.fill(result,1);
for(int i = 1; i < arrayLength; i++){
if(ratings[i-1] < ratings[i]){
result[i] = result[i-1]+1;
}
}
int acc = result[arrayLength-1];
// 右规则
for(int i = arrayLength -2 ; i >= 0; i--){
if(ratings[i] > ratings[i+1]){
result[i] = Math.max(result[i+1]+1,result[i]);
}
acc += result[i];
}
return acc;
}
}
七个不同的符号代表罗马数字,其值如下:
| 符号 | 值 |
|---|---|
| I | 1 |
| V | 5 |
| X | 10 |
| L | 50 |
| C | 100 |
| D | 500 |
| M | 1000 |
| 罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则: |
如果该值不是以 4 或 9 开头,请选择可以从输入中减去的最大值的符号,将该符号附加到结果,减去其值,然后将其余部分转换为罗马数字。
如果该值以 4 或 9 开头,使用 减法形式,表示从以下符号中减去一个符号,例如 4 是 5 (V) 减 1 (I): IV ,9 是 10 (X) 减 1 (I):IX。仅使用以下减法形式:4 (IV),9 (IX),40 (XL),90 (XC),400 (CD) 和 900 (CM)。
只有 10 的次方(I, X, C, M)最多可以连续附加 3 次以代表 10 的倍数。你不能多次附加 5 (V),50 (L) 或 500 (D)。如果需要将符号附加4次,请使用 减法形式。
给定一个整数,将其转换为罗马数字。
我的代码:
Java
class Solution {
public String intToRoman(int num) {
// 罗马数字不会超过3999
if(num > 3999){
return "error! num should less than 4000";
}
// 那么我们就从1000开始除即可
int division = num / 1000;
num %= 1000;
StringBuilder builder = new StringBuilder();
// 我觉得不用写那么多判断,直接依次处理就好
// 千位不可能是4或9
builder.append("M".repeat(division));
// 处理100
division = num / 100;
num %= 100;
if(division < 4){
builder.append("C".repeat(division));
}else if(division == 4){
builder.append("CD");
}else if(division == 9){
builder.append("CM");
}else{
builder.append("D").append("C".repeat(division - 5));
}
// 处理10
division = num / 10;
num %= 10;
if(division < 4){
builder.append("X".repeat(division));
}else if(division == 4){
builder.append("XL");
}else if(division == 9){
builder.append("XC");
}else{
builder.append("L").append("X".repeat(division - 5));
}
// 处理1
division = num / 1;
num %= 1;
if(division < 4){
builder.append("I".repeat(division));
}else if(division == 4){
builder.append("IV");
}else if(division == 9){
builder.append("IX");
}else{
builder.append("V").append("I".repeat(division - 5));
}
return builder.toString();
}
}
有点简单粗暴,实际上罗马数字的核心就是先取大的,再取小的,只是对于4和9有特殊规则而已
Java
class Solution {
private Map<Integer,String> map = new LinkedHashMap<Integer,String>(){{
put(1000,"M");
put(900,"CM");
put(500,"D");
put(400,"CD");
put(100,"C");
put(90,"XC");
put(50,"L");
put(40,"XL");
put(10,"X");
put(9,"IX");
put(5,"V");
put(4,"IV");
put(1,"I");
}};
public String intToRoman(int num) {
StringBuilder builder = new StringBuilder();
for(Map.Entry<Integer,String> entry : map.entrySet()){
int val = entry.getKey();
while(num >= val){
builder.append(entry.getValue());
num -= val;
}
}
return builder.toString();
}
}
但是这玩意儿没我的简单粗暴法快
接雨水
题目描述:
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
我的解法:
Java
class Solution {
public int trap(int[] height) {
// 有一个很简单朴素的"一格一格沉没"的算法
// 每次先去掉头尾的小于等于0的数,这样水就可以兜住
// 然后对每个值减一,如果小于等于0,则这里可以存一份水
// 然后继续去头尾重复直至全被淹没
// 实际采用两个指针来记录那些是能要的
int left = 0;
int right = height.length - 1;
int acc = 0;
while(left <= right){
// 去头尾
while(left <= right && height[left] <= 0 ){
left++;
}
while(left <= right && height[right] <= 0 ){
right--;
}
// 全都减一
for(int i = left; i <= right; i++){
height[i]--;
if(height[i] < 0){
acc++;
}
}
}
return acc;
}
}
假设数组长度为n,数组的最大数目为M,则时间复杂度为O(nM)
官方做法
Java
class Solution {
public int trap(int[] height) {
// 采用全后缀算法
// 使用两个辅助数组记录前面的最大桶高和后面的最大桶高
int length = height.length;
int[] preHeight = new int[length];
Arrays.fill(preHeight,0);
int[] sufHeight = new int[length];
Arrays.fill(sufHeight,0);
// preHeight[i] 记录第i个位置及其之前的 最大高度
int maxHeight = 0;
for(int i = 0; i < length; i++){
if(height[i] > maxHeight){
maxHeight = height[i];
}
preHeight[i] = maxHeight;
}
// 同理,sufHeight记录第i个位置及其之后的 最大高度
maxHeight = 0;
for(int i = length - 1; i >= 0; i--){
if(height[i] > maxHeight){
maxHeight = height[i];
}
sufHeight[i] = maxHeight;
}
// 从左到右进行遍历,每个格子能装的水
// 等于min(左边最高,右边最高) - 自己的高度
int acc = 0;
for(int i = 0; i < length; i++){
acc += Math.min(preHeight[i],sufHeight[i]) - height[i];
}
return acc;
}
}
还有去除辅助数组的更优的解法:
Java
class Solution {
public int trap(int[] height) {
// 还可以进行简化,使用指针代替辅助数组
// 由于前后缀在遍历过程中是不见减小的
// 而且存在短板效应
// 即对于前后缀中较大的部分,比如说现在suf为10,pre为2
// 还不能计算suf对应的单元格,因为有可能pre遍历到后面变大了
// 比如说pre遍历成了8,那么就实际可以接更多水
// 但是对于其中较小的,即pre,即使后面suf变大,也是按照小的pre来算
// 所有我们使用双向指针,相遇为止,每次算小的那个格子然后移动指针
int length = height.length;
int preMax = 0 ,sufMax = 0;
int acc = 0;
for(int left = 0, right = length - 1; left <= right; ){
preMax = Math.max(preMax,height[left]);
sufMax = Math.max(sufMax,height[right]);
if(preMax < sufMax){
acc += preMax - height[left];
left++;
}else{
acc += sufMax - height[right];
right--;
}
}
return acc;
}
}