算法之路
本系列随缘更新
第一章 [数据结构与算法] 邂逅数组与队列
第二章 [数据结构与算法] 邂逅链表
第三章 [数据结构与算法] 邂逅栈
第四章 [数据结构与算法] 排序算法
第五章 [数据结构与算法] 排序算法之冒泡排序与快速排序(快排)
第六章 [数据结构与算法] 排序算法之选择排序和堆排序
第七章 [数据结构与算法] 排序算法之直接插入排序与希尔排序
第八章 [数据结构与算法] 排序算法之归并排序与基数排序
第九章 [数据结构与算法] 查找算法
第十章 [数据结构与算法] 树结构之二叉树
第十一章 [数据结构与算法] 树结构之二叉排序树、平衡二叉树、多路查找树
第十二章 [数据结构与算法]赫夫曼树与赫夫曼编码
第十三章 [数据结构与算法] 图结构
第十四章 [数据结构与算法] 盘点工作中常用的算法
第十五章 [数据结构与算法] 输入当前是一周的第几天, 返回今天直到三天后分别都是星期几
一. 前言
对该问题进行抽象, 实际上就是是: 输入当前是星期几, 输出从今到几天后所有的星期数( 都是星期几 )
这个算法一种情况就是用于前端 在下拉选择时间框的时候, 设置几天内可预约时间
我们可以先将问题具体化: 输入当前是一周的第几天, 返回今天到3天之后分别都是星期几, 最后再进行抽象化
通过对问题的梳理, 来推敲简单算法实现的过程, 并举一反三对问题进行多方位思考
二. 分析
我们可以先将可能的情况写下来, 便于观察规律
//要求: 输入周几, 返回该天至该天后3天都是星期几
周一 1 2 3 4
周二 2 3 4 5
周三 3 4 5 6
周四 4 5 6 7
周五 5 6 7 1
周六 6 7 1 2
周日 7 1 2 3
注意:
- 本例因为可能性较少因此进行了穷举
- 如果元素较少, 可以考虑穷举; 如果元素较多, 则按照自己的想法去列举, 直至能发现其中的规律
根据上面规律, 我们很容易发现
- 当天在周一~周四, 天数介于
当天~当天+3
之间 - 当天在周五~周日, 天数介于
当天~周末
和周一 ~ 当天-4
之间
//周一 ~ 周四很好理解
//周五 ~ 周日的情况需要想想办法了
周五 5 6 7 1
周六 6 7 1 2
周日 7 1 2 3
//首尾对应看下, 可以看到下面对应关系 并不能在数组 或者 list中 通过连续遍历 获得
5-->1
6-->2
7-->3
//因此, 我们需要人为的去构建这种连续, 比如
8-7=1
9-7=2
10-7=3
//而8,9,10 和5,6,7 有什么关系呢?
那就是前者等于后者+3!!! 而这个3 正好对应的是几天后(时间段)
根据上面的猜想, 我们能够较为快速的想到
- 去构建一个 1 - 10连续的, 并且存放10个元素的数组
- 然后去按顺序遍历这些数组, 当数组元素大于7时, 减去7 即可( 这里的思路很关键 )
遍历的 开始是day-1 (作用是将当前星期几与上面数组建立联系, 数组下标从0开始)
遍历的 结束是: day+2. (因为 day-1, day, day+1, day+2 正好是当前天数到3天后的星期数)
判断遍历的结果, 小于7不变, 大于7 则直接 - 7 即可!!!
三. 实现
以上思路代码实现如下
/**
* 输入当前星期几, 输出当前到几天后的值
* @return
*/
public static List<Integer> getDayOfThreeDayAfter(int day) {
//初始化数据
Integer[] week = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
//动态初始化list
List<Integer> list = new ArrayList<>();
//四个时间段 d-1, d, d+1. d-2
for (int i = day-1; i <= day+ 2; i++) {
//判断遍历的结果, 小于7不变, 大于7 则直接 - 7
list.add(week[i] > 7 ? week[i] - 7 : week[i]);
}
return list;
}
public static void main(String[] args) {
/**
* 要求: 输入周几, 返回该天至该天后3天都是星期几
* 周一 1 2 3 4
* 周二 2 3 4 5
* 周三 3 4 5 6
* 周四 4 5 6 7
* 周五 5 6 7 1
* 周六 6 7 1 2
* 周日 7 1 2 3
*/
for (int i = 1; i <= 7; i++) {
System.out.println("getDayOfThreeDayAfter("+i+") = " + getDayOfThreeDayAfter(i));
}
}
测试结果
拓展
将当前方法进行抽象, 使其效果达到: 输入当前星期几, 以及时间间隔, 输出从当前到几天后的星期数(分别都是星期几)
核心注意点:
- 数组初始后的容量设置和动态赋值
- 数组遍历的初始值和结束值的设计
/**
* 输入当前星期几, 输出当前到几天后的值
* @param today 今天星期几
* @param days 几天后
* @return
*/
public static List<Integer> getDaysOfThreeDayAfter(int today, int days) {
//初始化数组
Integer[] week = new Integer[7+days];
for (int j = 0; j < 7 + days ; j++) {
week[j] = j + 1;
}
//动态初始化list
List<Integer> list = new ArrayList<>();
//四个时间段 d-1, d, d+1. d-2
for (int i = today - 1; i <= today + days-1; i++) {
//判断遍历的结果, 小于7不变, 大于7 则直接 - 7
list.add(week[i] > 7 ? week[i] - 7 : week[i]);
}
return list;
}
public static void main(String[] args) {
for (int i = 1; i <= 7; i++) {
System.out.println("getDaysOfThreeDayAfter(" + i + ") = " + getDaysOfThreeDayAfter(i, 4));
}
测试结果
我们回顾下上面代码的实现思路
1.去构建一个 1 - 10 ,连续的, 存放10个元素的数组
2. 然后去按顺序遍历这些数组, 当数组元素大于7时, 减去7 即可( 这里的思路很关键 )
遍历的 开始是day-1 (作用是将当前星期几与上面数组简历联系, 数组下标从0开始)
遍历的 结束是: day+2. (因为 day-1, day, day+1, day+2 正好是当前天数到3天后的星期数)
判断遍历的结果, 小于7不变, 大于7 则直接 - 7 即可!!!
灵光一闪
反向思考
既然上面的方式核心是在list 初始化后遍历取值的时候, 值的变化(小于7不变, 大于7 则直接 - 7)
那么我们为什么不可以将核心转移到 数组初始化后动态赋值的时候呢.
这样我们后面仅需要确定遍历的起始下班和结束下标即可
以上思考的思路如下:
- 构建一个包含1-10连续的, 存放10个元素的数组, 判断每个元素的值是否大于7, 大于7则-7, 小于7则不变
- 然后去按顺序遍历这些数组,
遍历的 开始是day-1 (作用是将当前星期几与上面数组简历联系, 数组下标从0开始)
遍历的 结束是day+2. (因为 day-1, day, day+1, day+2 正好是当前天数到3天后的星期数)
推广到该天到任意天数之间的星期数
- 构建一个包含1-
7+days
(days为时间段) 连续7+days
个元素的数组, 判断每个元素的值是否大于7, 大于7则-7, 小于7则不变 - 然后去按顺序遍历这些数组,
遍历的 开始是day-1 (作用是将当前星期几与上面数组简历联系, 数组下标从0开始)
因为 days=3, 结束坐标=today+3 -1 ;
days=4 ,结束坐标=today+4-1;
days=5, 结束坐标=today+5-1
遍历的 结束坐标是: today + days-1.
/**
* 拓展方式的另一种变种, 就是在初始化时, 就将数据初始好, 在list动态初始化时直接遍历即可
* 输入当前星期几, 输出当前到几天后的值
* @param today 今天星期几
* @param days 几天后
* @return
*/
public static List<Integer> getDaysOfThreeDayAfter2(int today, int days) {
//初始化数组
Integer[] week = new Integer[7+days];
for (int j = 0; j < 7 + days ; j++) {
//在数组初始化时, 判断j+1的值和7的关系: 小于7不变, 大于7 则直接 - 7
week[j] = (j + 1) > 7 ? (j + 1) - 7 : j + 1;
}
//动态初始化list
List<Integer> list = new ArrayList<>();
//四个时间段 d-1, d, d+1. d-2
for (int i = today - 1; i <= today + days-1; i++) {
list.add(week[i]);
}
return list;
}
public static void main(String[] args) {
for (int i = 1; i <= 7; i++) {
System.out.println("getDaysOfThreeDayAfter2(" + i + ") = " + getDaysOfThreeDayAfter2(i, 4));
}
}
补充: 建立星期和日期的映射, 用于在进行遍历时, 根据所属星期几设置当前时间
/**
* 输入当前星期几, 返回几天后的星期数与对应日期数
* 注意: 不能超过7天, 即days不能 >=6
* @param today
* @param days
* @return
*/
public static Map<Integer, String> getDaysOfThreeDayAfterAndDate(int today, int days) {
//初始化数组
Integer[] week = new Integer[7 + days];
for (int j = 0; j < 7 + days; j++) {
//在数组初始化时, 判断j+1的值和7的关系: 小于7不变, 大于7 则直接 - 7
week[j] = (j + 1) > 7 ? (j + 1) - 7 : j + 1;
}
//动态初始化list
List<Integer> list = new ArrayList<>();
//初始化Map 用户存放当前日期
Map<Integer, String> dateMap = new LinkedHashMap<>(16);
//四个时间段 d-1, d, d+1. d-2
for (int i = today - 1, k = 0; i <= today + days - 1; i++, k++) {
list.add(week[i]);
dateMap.put(week[i], LocalDateTime.now().toLocalDate().plusDays(k).toString());
}
return dateMap;
}
总结
- 个人认为, 相比拓展中
getDaysOfThreeDayAfter
对list 初始化后动态赋值并进行判断,
灵光一闪中getDaysOfThreeDayAfter2
对数组初始化后动态赋值更容易接受一些.
因为这样做将最难的问题, 赋值问题在一开始就解决, 剩下我们只需注意遍历时的坐标即可 - 从之前学习算法的经验以及自己造轮子(虽然比较简单)的经历可以体会到.
最核心问题是算法设计思路, 其次就是对数据进行赋值(数组初始化容量, 动态赋值)和遍历(起始和结束下标) .
只要这些点能够掌握, 算法就可能没有想象中的那么简单 - 之前从某位大佬那里学习到. 要从战略上藐视技术, 从战术上重视技术.
究其根本原因就是, 其实技术本来就是由繁入简.
最为难以搞定的机器语言, 汇编语言已经由业界前辈们为我们封装成黑盒代码, 我们直接调用对应api就可以使用了.
并且通过各种语言成熟的框架借以让更多人去轻松掌握技术, 从事技术, 进而推动业务发展, 适应更加复杂的业务情况
但是如果不去多多学习新技术, 去提高我们的开发效率, 我们就只能故步自封. 很可能无法应对今后的工作环境 - 因此无论多难的问题, 对其进行拆分, 然后对其各个击破, 最后几乎都能将其解决.
这也正契合大数据技术中 分而治之 的思想. - 私以为, 之所以都推荐学习底层源码和原理或者算法等原因是:
方便我们更好的理解技术/框架, 解决平时无法通过常规方式来解决的问题
万变不离其宗, 多多反思, 努力学习和体会技术发展的本质!!!