一、Algorithm
本周算法完成如下:
1.1 题目来源【Leetcode 31 下一个排列】
1.2 题目描述
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。 1,2,3 → 1,3,2 3,2,1 → 1,2,3 1,1,5 → 1,5,1
1.3 解题如下:
package cn.mayday.algorithms.day20191212;
import java.util.Arrays;
/**
* 实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
* <p>
* 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
* <p>
* 必须原地修改,只允许使用额外常数空间。
* <p>
* 以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
* 1,2,3 → 1,3,2
* 3,2,1 → 1,2,3
* 1,1,5 → 1,5,1
*/
public class Leetcode31NextPermutation {
/**
* 测试函数
*
* @param args
*/
public static void main(String[] args) {
Leetcode31NextPermutation permutation = new Leetcode31NextPermutation();
int[] nums1 = new int[]{1, 2, 3};
permutation.nextPermutation(nums1);
System.out.println("result1" + Arrays.toString(nums1)); // 1、3、2
int[] nums2 = {3, 2, 1};
permutation.nextPermutation(nums2);
System.out.println("result2" + Arrays.toString(nums2)); // 1、2、3
int[] nums3 = {1, 1, 5};
permutation.nextPermutation(nums3);
System.out.println("result3" + Arrays.toString(nums3)); // 1、5、1
int[] nums4 = {1, 3, 2};
permutation.nextPermutation(nums4);
System.out.println("result3" + Arrays.toString(nums4)); // 2、1、3
}
/**
* 实现方法
*
* @param nums
*/
public void nextPermutation(int[] nums) {
/**
* 1 找到逆序区边界
* 2 逆序区的前一个数值和逆序区最小值替换位置
* 3 逆序区重排序
*/
int length = nums.length;
int reverseIndex = findReverseIndex(nums);
// System.out.println("逆序索引:" + reverseIndex);
if (reverseIndex == 0) {
// 如果等于0,说明全部逆序,直接输出最小值即可
} else {
// 如果不等于0,将逆序区的前一位和逆序区中刚刚大于它的数值交换位置即可
switchHeadNum(nums, reverseIndex);
}
// 冒泡排序--逆序区
boolean isChanged = true;
for (int i = reverseIndex; i < length; i++) {
for (int j = i + 1; j < length; j++) {
if (nums[i] > nums[j]) {
int temp = nums[j];
nums[j] = nums[i];
nums[i] = temp;
isChanged = false;
}
}
if (isChanged) {
break;
}
}
}
/**
* 查询出现逆序时的前一个索引。也就是该索引后数据都是顺序
*
* @param nums
* @return
*/
private int findReverseIndex(int[] nums) {
for (int i = nums.length - 1; i > 0; i--) {
if (nums[i - 1] < nums[i]) {
return i;
}
}
return 0;
}
/**
* 交换第一个逆序和第一个比它大的数字位置
*
* @param nums
* @param reverseIndex
*/
private void switchHeadNum(int nums[], int reverseIndex) {
int changedNum = nums[reverseIndex - 1];
// 从逆序最后一个开始找,肯定能找到刚刚大于他的数字
for (int i = nums.length - 1; i >= reverseIndex; i--) {
if (nums[i] > changedNum) {
nums[reverseIndex - 1] = nums[i];
nums[i] = changedNum;
break;
}
}
}
}
1.4 总结
这道题考察对问题的理解,如何找到下一组排列,归根到底为字典序算法。
字典序,就是按照字典中出现的先后顺序进行排序。
字典序算法用来解决这样一个问题:给定其中一种排列,求基于字典序的下一种排列。
详细说明见字典序算法详解
二、Review
2.1 文章内容
Facebook的论文《Scaling Memcache at Facebook》
本篇文章介绍了FaceBook在缓存方面的设计结构和优化,以及数据对比。(还没看完,下周继续)
三、Tip
学习使用了阿里巴巴 开源的Arthas 工具,强烈推荐。
3.1 该工具具有以下功能
当你遇到以下类似问题而束手无策时,Arthas 可以帮助你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态?
3.2 对于个人目前最经常使用的功能:
Watch功能
通过watch
命令可以查看函数的参数/返回值/异常信息。
watch demo.MathGame primeFactors returnObj
输入 Q
或者 Ctrl+C
退出watch命令。
3.3 高阶特性
3.4 官方学习文档
https://alibaba.github.io/arthas/
四、Share
本周分享文章为浩子叔的缓存更新的文章
4.1 文章内容
文章内容见下面链接
缓存更新的套路—左耳朵耗子
4.2 总结思考
(1)更新缓存的四种Design Pattern
- Cache aside—-旁路缓存
- Read through—-读缓存穿透
- Write through—写缓存穿透
- Write behind caching—先更新缓存在更新持久层【Linux文件系统的Page Cache的算法】
这些Design Pattern,其实并不是软件架构里的mysql数据库和memcache/redis的更新策略,这些东西都是计算机体系结构里的设计,比如CPU的缓存,硬盘文件系统中的缓存,硬盘上的缓存,数据库中的缓存。基本上来说,这些缓存更新的设计模式都是非常老古董的,而且历经长时间考验的策略,所以这也就是,工程学上所谓的Best Practice,遵从就好了
(2)FaceBook的做法
-
先更新数据库,再删除缓存
-
设置缓存实现时间
是不是Cache Aside这个就不会有并发问题了?不是的,比如,一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。
但,这个case理论上会出现,不过,实际上出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。
4.3 扩展内容
通过2PC或是Paxos协议保证一致性如何实现 下次补充