learn.lianglianglee.com/专栏/重学数据结构与算法-完/加餐 课后练习题详解.md.html
2022-08-14 03:40:33 +08:00

456 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>加餐 课后练习题详解.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/重学数据结构与算法-完/00 数据结构与算法,应该这样学!.md.html">00 数据结构与算法,应该这样学!</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/01 复杂度:如何衡量程序运行的效率?.md.html">01 复杂度:如何衡量程序运行的效率?</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/02 数据结构:将“昂贵”的时间复杂度转换成“廉价”的空间复杂度.md.html">02 数据结构:将“昂贵”的时间复杂度转换成“廉价”的空间复杂度</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/03 增删查:掌握数据处理的基本操作,以不变应万变.md.html">03 增删查:掌握数据处理的基本操作,以不变应万变</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/04 如何完成线性表结构下的增删查?.md.html">04 如何完成线性表结构下的增删查?</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/05 栈:后进先出的线性表,如何实现增删查?.md.html">05 栈:后进先出的线性表,如何实现增删查?</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/06 队列:先进先出的线性表,如何实现增删查?.md.html">06 队列:先进先出的线性表,如何实现增删查?</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/07 数组:如何实现基于索引的查找?.md.html">07 数组:如何实现基于索引的查找?</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/08 字符串:如何正确回答面试中高频考察的字符串匹配算法?.md.html">08 字符串:如何正确回答面试中高频考察的字符串匹配算法?</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/09 树和二叉树:分支关系与层次结构下,如何有效实现增删查?.md.html">09 树和二叉树:分支关系与层次结构下,如何有效实现增删查?</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/10 哈希表:如何利用好高效率查找的“利器”?.md.html">10 哈希表:如何利用好高效率查找的“利器”?</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/11 递归:如何利用递归求解汉诺塔问题?.md.html">11 递归:如何利用递归求解汉诺塔问题?</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/12 分治:如何利用分治法完成数据查找?.md.html">12 分治:如何利用分治法完成数据查找?</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/13 排序:经典排序算法原理解析与优劣对比.md.html">13 排序:经典排序算法原理解析与优劣对比</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/14 动态规划:如何通过最优子结构,完成复杂问题求解?.md.html">14 动态规划:如何通过最优子结构,完成复杂问题求解?</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/15 定位问题才能更好地解决问题:开发前的复杂度分析与技术选型.md.html">15 定位问题才能更好地解决问题:开发前的复杂度分析与技术选型</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/16 真题案例(一):算法思维训练.md.html">16 真题案例(一):算法思维训练</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/17 真题案例(二):数据结构训练.md.html">17 真题案例(二):数据结构训练</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/18 真题案例(三):力扣真题训练.md.html">18 真题案例(三):力扣真题训练</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/19 真题案例(四):大厂真题实战演练.md.html">19 真题案例(四):大厂真题实战演练</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/20 代码之外,技术面试中你应该具备哪些软素质?.md.html">20 代码之外,技术面试中你应该具备哪些软素质?</a>
</li>
<li>
<a href="/专栏/重学数据结构与算法-完/21 面试中如何建立全局观,快速完成优质的手写代码?.md.html">21 面试中如何建立全局观,快速完成优质的手写代码?</a>
</li>
<li>
<a class="current-tab" href="/专栏/重学数据结构与算法-完/加餐 课后练习题详解.md.html">加餐 课后练习题详解</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>加餐 课后练习题详解</h1>
<p>专栏虽已结束,但学习不应停止。我看到很多同学依然还在这里学习,一部分同学积极地在留言区和大家分享学习总结和练习题答案。</p>
<p>我几乎在每个课时的结尾都留下了一道练习题,目的是帮助你检测和巩固本课时的重点内容,抑或是引出后续课时中的内容。在我处理留言的过程中,发现很多同学想要练习题详细解答过程以及答案,所以就有了今天的这一篇加餐内容,希望对你有所帮助。</p>
<p>接下来我会给出每个课时练习题的解题思路和答案,如果你没有找到对应的练习题答案,那么请你在正课中查找。</p>
<h4>01 | 复杂度:如何衡量程序运行的效率?</h4>
<p><strong>【问题】</strong> 评估一下,如下的代码片段,时间复杂度是多少?</p>
<pre><code>for (i = 0; i &lt; n; i++) {
for (j = 0; j &lt; n; j++) {
for (k = 0; k &lt; n; k++) {
}
for (m = 0; m &lt; n; m++) {
}
}
}
</code></pre>
<p><strong>【解析】</strong> 在上面的代码中:</p>
<ul>
<li>第 35 行和 68 行,显然是一个 O(n) 复杂度的循环。这两个循环是顺序结构,因此合在一起的复杂度是 O(n) + O(n) = O(2n) = O(n)。</li>
<li>第 29 行是一个 for 循环,它的时间复杂度是 O(n)。这个 for 循环内部嵌套了 O(n) 复杂度的代码,因此合在一起就是 O(n ² ) 的时间复杂度。</li>
<li>在代码的最外部,第 110 行又是一个 O(n) 复杂度的循环,内部嵌套了 O(n ² ) 的时间复杂度的代码。因此合在一起就是 O(n ³ ) 的时间复杂度。</li>
</ul>
<h4>02 | 数据结构:将“昂贵”的时间复杂度转换成“廉价”的空间复杂度</h4>
<p><strong>【问题】</strong> 在下面这段代码中,如果要降低代码的执行时间,第 4 行代码需要做哪些改动呢?如果做出改动后,是否降低了时间复杂度呢?</p>
<pre><code>public void s2_2() {
int count = 0;
for (int i = 0; i &lt;= (100 / 7); i++) {
for (int j = 0; j &lt;= (100 / 3); j++) {
if ((100-i*7-j*3 &gt;= 0)&amp;&amp;((100-i*7-j*3) % 2 == 0)) {
count += 1;
}
}
}
System.out.println(count);
}
</code></pre>
<p><strong>【解析】</strong> 代码的第 4 行可以改为:</p>
<pre><code>for (int j = 0; j &lt;= (100-7*i) / 3; j++) {
</code></pre>
<p>代码改造完成后,时间复杂度并没有变小。但由于减少了一些不必要的计算量,程序的执行时间变少了。</p>
<h4>03 | 增删查:掌握数据处理的基本操作,以不变应万变</h4>
<p><strong>【问题】</strong> 对于一个包含 5 个元素的数组,如果要把这个数组元素的顺序翻转过来。你可以试着分析该过程需要对数据进行哪些操作?</p>
<p><strong>【解析】</strong> 假设原数组 a = {1,2,3,4,5},现在要更改为 a = {5,4,3,2,1}。要想得到新的数组,就要找到 “1” 和 “5”再分别把它们赋值给对方。因此这里主要会产生大量的基于索引位置的查找动作。</p>
<h4>04 | 如何完成线性表结构下的增删查?</h4>
<p><strong>【问题】</strong> 给定一个包含 n 个元素的链表,现在要求每 k 个节点一组进行翻转,打印翻转后的链表结果。例如,链表为 1 -&gt; 2 -&gt; 3 -&gt; 4 -&gt; 5 -&gt; 6k = 3则打印 321654。</p>
<p><strong>【解析】</strong> 我们给出一些提示。利用链表翻转的算法,这个问题应该很简单。利用 3 个指针prev、curr、next执行链表翻转每次得到了 k 个翻转的结点就执行打印。</p>
<h4>05 | 栈:后进先出的线性表,如何实现增删查?</h4>
<p><strong>【问题】</strong> 给定一个包含 n 个元素的链表,现在要求每 k 个节点一组进行翻转,打印翻转后的链表结果。例如,链表为 1 -&gt; 2 -&gt; 3 -&gt; 4 -&gt; 5 -&gt; 6k = 3则打印 321654。要求用栈来实现。</p>
<p><strong>【解析】</strong> 我们用栈来实现它,就很简单了。你可以牢牢记住,<strong>只要涉及翻转动作的题目,都是使用栈来解决的强烈信号</strong></p>
<p>具体的操作如下,设置一个栈,不断将队列数据入栈,并且实时记录栈的大小。当栈的大小达到 k 的时候,全部出栈。我们给出核心代码:</p>
<pre><code>while (tmp != null &amp;&amp; count &lt; k) {
stack.push(tmp.value);
tmp = tmp.next;
count++;
}
while (!stack.isEmpty()) {
System.out.print(stack.pop());
}
</code></pre>
<h4>07 | 数组:如何实现基于索引的查找?</h4>
<p><strong>详细分析和答案,请翻阅 18 课时例题 1</strong></p>
<h4>08 | 字符串:如何正确回答面试中高频考察的字符串匹配算法?</h4>
<p><strong>详细分析和解题步骤,请参考 17 课时例题 1。</strong></p>
<h4>10 | 哈希表:如何利用好高效率查找的“利器”?</h4>
<p><strong>详细分析和答案,请翻阅 15 课时例题 1</strong></p>
<h4>11 | 递归:如何利用递归求解汉诺塔问题?</h4>
<p><strong>详细分析和答案,请翻阅 16 课时例题 1</strong></p>
<h4>12 | 分治:如何利用分治法完成数据查找?</h4>
<p><strong>【问题】</strong> 在一个有序数组中,查找出第一个大于 9 的数字假设一定存在。例如arr = { -1, 3, 3, 7, 10, 14, 14 };则返回 10。</p>
<p><strong>【解析】</strong> 在这里提醒一下,带查找的目标数字具备这样的性质:</p>
<ul>
<li>第一,它比 9 大;</li>
<li>第二,它前面的数字(除非它是第一个数字),比 9 小。</li>
</ul>
<p>因此,当我们作出向左走或向右走的决策时,必须满足这两个条件。代码如下:</p>
<pre><code>public static void main(String[] args) {
int targetNumb = 9;
// 目标有序数组
int[] arr = { -1, 3, 3, 7, 10, 14, 14 };
int middle = 0;
int low = 0;
int high = arr.length - 1;
while (low &lt;= high) {
middle = (high + low) / 2;
if (arr[middle] &gt; targetNumb &amp;&amp; (middle == 0 || arr[middle - 1] &lt;= targetNumb)) {
System.out.println(&quot;第一个比 &quot; + targetNumb + &quot; 大的数字是 &quot; + arr[middle]);
break;
} else if (arr[middle] &gt; targetNumb) {
// 说明该数在low~middle之间
high = middle - 1;
} else {
// 说明该数在middle~high之间
low = middle + 1;
}
}
}
</code></pre>
<h4>14 | 动态规划:如何通过最优子结构,完成复杂问题求解?</h4>
<p><strong>详细分析和答案,请翻阅 16 课时例题 3</strong></p>
<h4>15 | 定位问题才能更好地解决问题:开发前的复杂度分析与技术选型</h4>
<p><strong>【问题】</strong> 下面的代码采用了两个 for 循环去实现 two sums。那么能否只使用一个 for 循环完成呢?</p>
<pre><code>private static int[] twoSum(int[] arr, int target) {
Map&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;();
for (int i = 0; i &lt; arr.length; i++) {
map.put(arr[i], i);
}
for (int i = 0; i &lt; arr.length; i++) {
int complement = target - arr[i];
if (map.containsKey(complement) &amp;&amp; map.get(complement) != i) {
return new int[] { map.get(complement), i };
}
}
return null;
}
</code></pre>
<p><strong>【解析】</strong> 原代码中,第 3 和第 6 行的 for 循环合并后,就需要把 map 的新增、查找合在一起执行。则代码如下:</p>
<pre><code>private static int[] twoSum(int[] arr, int target) {
Map&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;();
for (int i = 0; i &lt; arr.length; i++) {
int complement = target - arr[i];
if (map.containsKey(complement) &amp;&amp; map.get(complement) != i) {
return new int[] { map.get(complement), i };
}
else{
map.put(arr[i], i);
}
}
return null;
}
</code></pre>
<h4>16 | 真题案例(一):算法思维训练</h4>
<p><strong>【问题】</strong> 如果现在是个线上实时交互的系统。客户端输入 x服务端返回斐波那契数列中的第 x 位。那么,这个问题使用上面的解法是否可行。</p>
<p><strong>【解析】</strong> 这里给你一个小提示,既然我这么问,答案显然是不可行的。如果不可行,原因是什么呢?我们又该如何解决?</p>
<p>注意,题目中给出的是一个实时系统。当用户提交了 x如果在几秒内没有得到系统响应用户就会卸载 App 啦。</p>
<p>一个实时系统,必须想方设法在 O(1) 时间复杂度内返回结果。因此,一个可行的方式是,在系统上线之前,把输入 x 在 1100 的结果预先就计算完,并且保存在数组里。当收到 1100 范围内输入时O(1) 时间内就可以返回。如果不在这个范围,则需要计算。计算之后的结果返回给用户,并在数组中进行保存。以方便后续同样输入时,能在 O(1) 时间内可以返回。</p>
<h4>17 | 真题案例(二):数据结构训练</h4>
<p><strong>【问题】</strong> 对于树的层次遍历,我们再拓展一下。如果要打印的不是层次,而是蛇形遍历,又该如何实现呢?蛇形遍历就是 s 形遍历,即奇数层从左到右,偶数层从右到左。</p>
<p><strong>【解析】</strong> 这里要对数据的顺序进行逆序处理,直观上,你需要立马想到栈。毕竟只有栈是后进先出的结构,是能快速实现逆序的。具体而言,需要建立两个栈 s1 和 s2。进栈的顺序是s1 先右后左s2 先左后右。两个栈交替出栈的结果就是 s 形遍历,代码如下:</p>
<pre><code>public ArrayList&lt;ArrayList&lt;Integer&gt;&gt; Print(TreeNodes pRoot) {
// 先右后左
Stack&lt;TreeNodes&gt; s1 = new Stack&lt;TreeNodes&gt;();
// 先左后右
Stack&lt;TreeNodes&gt; s2 = new Stack&lt;TreeNodes&gt;();
ArrayList&lt;ArrayList&lt;Integer&gt;&gt; list = new ArrayList&lt;ArrayList&lt;Integer&gt;&gt;();
list.add(pRoot.val);
s1.push(pRoot);
while (s1.isEmpty() || s2.isEmpty()) {
if (s1.isEmpty() &amp;&amp; s2.isEmpty()) {
break;
}
if (s2.isEmpty()) {
while (!s1.isEmpty()) {
if (s1.peek().right != null) {
list.add(s1.peek().right.val);
s2.push(s1.peek().right);
}
if (s1.peek().left != null) {
list.add(s1.peek().left.val);
s2.push(s1.peek().left);
}
s1.pop();
}
} else {
while (!s2.isEmpty()) {
if (s2.peek().left != null) {
list.add(s2.peek().left.val);
s1.push(s2.peek().left);
}
if (s2.peek().right != null) {
list.add(s2.peek().right.val);
s1.push(s2.peek().right);
}
s2.pop();
}
}
}
return list;
}
</code></pre>
<h4>18 | 真题案例(三): 力扣真题训练</h4>
<p><strong>【问题】</strong> 给定一个链表,删除链表的倒数第 n 个节点。例如,给定一个链表: 1 -&gt; 2 -&gt; 3 -&gt; 4 -&gt; 5, 和 n = 2。当删除了倒数第二个节点后链表变为 1 -&gt; 2 -&gt; 3 -&gt; 5。你可以假设给定的 n 是有效的。额外要求就是,要在一趟扫描中实现,即时间复杂度是 O(n)。这里给你一个提示,可以采用快慢指针的方法。</p>
<p><strong>【解析】</strong> 定义快慢指针slow 和 fast 并同时指向 header。然后让 fast 指针先走 n 步。接着让二者保持同样的速度一起往前走。最后fast 指针先到达终点,并指向了 null。此时slow 指针的结果就是倒数第 n 个结点。比较简单,我们就不给代码了。</p>
<h4>19 | 真题案例(四):大厂真题实战演练</h4>
<p><strong>【问题】</strong> 小明从小就喜欢数学,喜欢在笔记里记录很多表达式。他觉得现在的表达式写法很麻烦,为了提高运算符优先级,不得不添加很多括号。如果不小心漏了一个右括号的话,就差之毫厘,谬之千里了。因此他改用前缀表达式,例如把 <code>(2 + 3) * 4</code>写成<code>* + 2 3 4</code>,这样就能避免使用括号了。这样的表达式虽然书写简单,但计算却不够直观。请你写一个程序帮他计算这些前缀表达式。</p>
<p><strong>【解析】</strong> 在这个题目中,输入就是前缀表达式,输出就是计算的结果。你可以假设除法为整除,即 “5/3 = 1”。例如输入字符串为 + 2 3输出 5输入字符串为 * + 2 2 3输出为 12输入字符串为 * 2 + 2 3输出为 10。</p>
<p>假设输入字符串为 * 2 + 2 3即 2*(2+3)。第一个字符为运算符号 *,它将对两个数字进行乘法。如果后面紧接着的字符不全是数字字符,那就需要暂存下来,先计算后面的算式。一旦后面的计算完成,就需要接着从后往前去继续计算。因为从后往前是一种逆序动作,我们能够很自然地想到可以用栈的数据结构进行存储。代码如下:</p>
<pre><code>public static void main(String[] args) {
Stack&lt;Object&gt; stack = new Stack&lt;Object&gt;();
String s = &quot;* + 2 2 3&quot;;
String attr[] = s.split(&quot; &quot;);
for (int i = attr.length - 1; i &gt;= 0; i--) {
if (!(attr[i].equals(&quot;+&quot;) || attr[i].equals(&quot;-&quot;) || attr[i].equals(&quot;*&quot;) || attr[i].equals(&quot;/&quot;))) {
stack.push(Integer.parseInt(attr[i]));
} else {
int a = (int) stack.pop();// 出栈
int b = (int) stack.pop();// 出栈
int result = Cal(a, b, attr[i]); // 调用函数计算结果值
stack.push(result); // 结果进栈
}
}
int ans = (int) stack.pop();
System.out.print(ans);
}
public static int Cal(int a, int b, String s) {
switch (s) {
case &quot;+&quot;:
return a + b;
case &quot;-&quot;:
return a - b;
case &quot;*&quot;:
return a * b;
case &quot;/&quot;:
return a / b;
}
return 0;
}
</code></pre>
<p>以上这些练习题你做得怎么样呢?还能回忆起来每道题是源自哪个算法知识点或哪个课时吗?</p>
<p>你可以把课后习题和课程中的案例都当作一个小项目,自己动手实践,即使有些题目你还不能写出完整的代码,那也可以尝试写出解题思路,从看不明白到能够理解,再到能联想到用什么数据结构和算法去解决什么样的问题,这是一个循序渐进的过程,切勿着急。</p>
<p>通过留言可以看出,你们都在认真地学习这门课程,也正因如此,我才愿意付出更多的时间优化这个已经完结的专栏。所以,请你不要犹豫,尽管畅所欲言,在留言区留下你的思考,也欢迎你积极地提问,更欢迎你为专栏提出建议,这样我才能更直接地看到你们的想法和收获。也许你的一条留言,就是下一篇加餐的主题。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/重学数据结构与算法-完/21 面试中如何建立全局观,快速完成优质的手写代码?.md.html">上一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997deedfb33cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>