每周完成一个 ARTS:
Algorithm 来源 LeetCode 11. Container With Most Water
Review 阅读了关于 Go 语言的一篇文章
Tip 总结 Unix 套接字编程的知识
Share 分享 一个分类详细的 Unix / Linux 命令网站
一 Algorithm
LeetCode 11. Container With Most Water 链接 难度:[Medium]
【题意】
Given n non-negative integers a1, a2, …, an , where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.
给定 n 个非负整数 a1,a2,…,an,其中每个代表一个点坐标(i,ai)。 n 个垂直线段例如线段的两个端点在(i,ai)和(i,0)。 找到两个线段,与 x 轴形成一个容器,使其包含最多的水。
Note: You may not slant the container and n is at least 2.
Example:
12 |
Input: [1,8,6,2,5,4,8,3,7]Output: 49 |
【思路】
双指针法
- 分别定义两个指针,或者变量从两边往中间扫描,然后这里需要注意的是,对于两条直立竖起的线段,中间能够储存的水量的多少一定取决于最短的那块线段,因为一旦超过了最短的那块线段,水就溢出来了。理解了这点,此题也就会做了。
- 将每次扫描的两个指针指向的线段的高度和水平距离之间的差值的乘积就是计算的到的临时最大水量,也就是(min(height[i],height[j])*(j-i))。
- 最后一边计算得到临时最大水量一边更新最大值便是答案。
时间复杂度 O(n),空间复杂度 O(1)。
【参考代码】
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758 |
Author: rongweiheTime: 2018-12-29*/ using namespace std;class {public: int maxArea(vector<int>& height) { int size = height.size(); int ret = 0; int i = 0; int j = size - 1; while( i<j ){ ret = max(ret,min(height[i],height[j])*(j-i)); height[i]<height[j]?++i:--j; } return ret; } }; void trimLeftTrailingSpaces(string &input) { input.erase(input.begin(), find_if(input.begin(), input.end(), [](int ch) { return !isspace(ch); }));} void trimRightTrailingSpaces(string &input) { input.erase(find_if(input.rbegin(), input.rend(), [](int ch) { return !isspace(ch); }).base(), input.end());} vector<int> stringToIntegerVector(string input) { vector<int> output; trimLeftTrailingSpaces(input); trimRightTrailingSpaces(input); input = input.substr(1, input.length() - 2); stringstream ss; ss.str(input); string item; char delim = ','; while (getline(ss, item, delim)) { output.push_back(stoi(item)); } return output;} int main() { string line; while (getline(cin, line)) { vector<int> height = stringToIntegerVector(line); int ret = Solution().maxArea(height); string out = to_string(ret); cout << out << endl; } return 0;} |
二 Review
本次 Review 阅读了关于 Go 语言的一篇文章: Concurrency in Golang(英文)。
(PS:参考引用自 https://github.com/zouyingjie/arts/blob/master/2018-12-11.md 的 Review 部分)。
觉得写得很不错,分享给大家。
作者大致讲述了自己对 Go 语言中并发模型的理解。Go 语言的并发模型是基于 CSP(Communicating Sequential Processes),在 Go 语言的世界里,流传着这样一句名言:
Do not communicate by sharing memory; instead, share memory by communicating. 不要通过共享内存来通信,而应该通过通信来共享内存
这两句话到底意味着什么呢?下面是作者对此的理解:
1. Do not communicate by sharing memory
熟悉 C++/Java 等面向对象的语言的同学都知道,在多数情况下,我们需要通过多线程来实现并发。对于某些多个线程都要用到的数据,我们需要使用锁来避免同一时间多个线程访问同一个数据。
为什么要加锁?很简单,是为了共享数据段操作的互斥。
何时需要加锁?在形成资源竞争的时候,也就是说,多个线程有可能访问同一共享资源的时候。
其实就是锁住了某块在线程间共享内存,避免该块内存被同时访问,这就是所谓的 communicate by sharing memory 。但是这种方式会造成锁的竞争、内存管理、死锁以及难以解释的随机唤醒等问题。
2. share memory by communicating
既然传统的加锁共享内存机制存在问题,Go 语言又是如何实现并发的呢?
Go 语言允许我们将数据从一个线程发送到另一个线程。类似于 Unix 下 I/O 多路复用的原理。当一个线程处理完数据后将数据发出,另一个线程等待接收数据。当数据没有处理完时,线程会一直等待,这样就大大降低了出现锁冲突的概率。这就是所谓的 share memory by communicating。
下面是一段简单的例子,通过开启两个协程和通道,简单说明 Go 语言进行数据的发送和处理。
12345678910111213141516171819202122232425262728293031323334353637383940 |
package main import ( "fmt" "time") func main() { // 创建发送数据的通道 ch := make(chan int) // 接收数据的通道 done := make(chan bool) // 开启协程 go sendingGoRoutine(ch) go receivingGoRoutine(ch,done) // 这里会阻塞,直到数据接收完毕 <- done} func sendingGoRoutine(ch chan int){ //start a timer to wait 5 seconds t := time.NewTimer(time.Second*5)大专栏 ARTS/> <- t.C fmt.Println("Sending a value on a channel") // 发送数据 ch <- 45} func receivingGoRoutine(ch chan int, done chan bool){ // 接收 ch 通道的数据 v := <- ch fmt.Println("Received value ", v) // 处理完成后向 done 通道发送数据告知操作已完成 done <- true} |
个人 Review
在使用 Go 的并发模型时,需要注意的地方是避免过度使用,某些地方还是需要通过传统的锁机制来实现的,比如引用计数、文件读写等,这些都可以通过 Go 提供的 sync 包实现。
阿尔伯特-爱因斯坦曾经说过“学习一门新知识之后,如果你还不能简单地解释它,说明你并没有理解它。”
对于任何一门新语言或者新知识,如果我们学习之后还不能将其中的要点通俗易懂地给别人讲清楚,那只能说明我们掌握的还不够,理解的还不深。及时复习和总结归纳显得尤为必要。
三 Tip
Unix 套接字编程系列之套接字介绍
在 Linux 系统中,有很多进程间通信方式,套接字(Socket)就是其中的一种。
由于项目涉及到 Linux 网络编程的知识,最近又重新看了一下《Unix网络编程 第三版》,将一些学习笔记总结一下。
1.套接字地址结构
大多数套接字函数都需要一个指向套接字地址结构的指针作为参数。每个协议族都定义它自己的套接字地址结构。这些结构的名字均以sockaddr_开头,并以对应每个协议族的唯一后缀结尾。
1.要学习套接字编程,就要先理解套接字地址结构,这些结构可以在两个方向传递:从进程到内核和从内核到进程。其中从内核到进程方向传递是值-结果参数。
2.地址转换函数在地址的文本表达和它们存放在套接字地址结构中的二进制值之间进行转换,多数现存的 IPv4 代码使用 inet_addr 和 inet_ntoa 这两个函数,但是有最新的函数 inet_pton 和 inet_ntop 同时适用于IPv4 和 IPv6 两种代码。要注意的是,地址转换函数存在的一个问题是它们与所转换的地址类型协议相关。
首先来看一下不同的套接字地址结构
IPv4 套接字地址结构
IPv4 套接字地址结构通常称为“网际套接字地址结构”,它以 sockaddr_in 命名,定义在 <netinet/in.h> 头文件中,下图给出了它的 POSIX 定义。
123456789101112131415 |
struct in_addr { in_addr_t s_addr; /* 32-bit IPv4 address */}; /* network byet orered */ stuct sockaddr_in{ uint8_t sin_len; /* length of structure */ sa_familyt sin_family; /* AF_INET */ in_port_t sin_port; /* 16-bit TCP or UDP port number */ /* network byet orered */ struct in_addr sin_addr; /* 32-bit IPv4 address */ /* network byet orered */ char sin_zero[8] /* unused */}; |
当作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用形式(也就是以指向该结构的指针)来传递。然而以这样的指针作为参数之一的任何套接字函数必须处理来自所支持的任何协议族的套接字地址结构。
对套接字地址结构做几点一般性的说明
长度字段 sin_len 是为了增加对 OSI 协议的支持而添加的,在此之前,第一个成员是 sin_family ,它是一个无符号短整数(unsigned short)。并不是所有的厂家都支持套接字地址结构的长度字段,而且 POSIX 规范也不要求有这个成员。该成员的数据类型是 uint8_t 。正是因为有了长度字段,才简化了长度可变套接字地址结构的处理。
POSIX 的规范只需要这个结构中的 3 个字段:sin_family,sin_addr,sin_port。对于符合 POSIX 的实现来说,定义额外的结构字段是可以接受的,这对于网际套接字地址结构来说也都是正常的,几乎所有的实现都增加了 sin_zero 字段,所以所有的套接字地址结构大小都至少是 16 字节。
IPv4 地址和 TCP 或 UDP 端口号在套接字地址结构中总是以网络字节序来存储。在使用这些字段时,我们必须牢记这一点。
套接字地址结构仅在给定主机上使用:虽然结构中的某些字段(例如 IP 地址和端口号)用在不同主机之间的通信中,但是结构本身并不在主机之间传递。
1.2 通用的套接字地址结构
当作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用形式(也就是以指向该结构的指针)来传递。为了以这样的指针作为参数之一的任何套接字函数必须处理来自所支持的任何协议族的套接字地址结构,在<sys/socket.h>头文件中定义了一个通用的套接字地址结构。
123456 |
struct sockaddr{ uint8_t sa_len; sa_family_t sa_family; /* address family: AF_XXX value */ char sa_data[14]; /* protocol-specific address */}; |
这些通用套接字地址结构的唯一用途就是对指向特定于协议的套接字地址结构的指针执行类型强制转换。
IPv6 套接字地址结构
定义在 <netinet/in.h> 头文件中
12345678910111213141516171819 |
struct in6_addr{ uint8_t s6_addr[16]; /* 128-bit IPv6 address */ /* network byet orered */}; #define SIN6_LEN /* required for compile-time tests */ stuct sockaddr_in6{ uint8_t sin6_len; /* length of this structure (28)*/ sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* transport layer port# */ /* network byet orered */ uint32_t sin6_flowinfo; /* flow information,undefined */ struct in6_addr sin6_addr; /* IPv6 address */ /* network byet orered */ uint32_t sin6_scope_id; /* set of interfaces for a scope */ }; |
- 如果系统支持套接字地址结构中的长度字段,那么SIN6_LEN常值必须定义。
- IPv6的地址族是AF_INET6,而IPv4的地址族是AF_INET。
1.4 新的通用套接字地址结构
作为 IPv6 套接字 API 的一部分而定义的新的通用套接字地址结构克服了现有 stuct sockaddr 的 一些缺点。不像 stuct sockaddr,新的 struct sockaddr_storage 足以容纳系统所支持的任何套接字地址结构。该结构在<netinet/in.h> 头文件中定义。
1234567891011 |
struct sockaddr_storage{ uint8_t ss_len; /* length of this structure (implementation dependent)*/ sa_family_t ss_family; /* address family :AF_xxx value */ /* implementation-dependent elements to provide: * a) alignment sufficient to fulfill the alignment requirements of * all socket address types that the system supports. * b) enough storage to hold any type of socket address that the * system supports. */}; |
如果系统支持的任何套接字地址结构有对齐需要,那么 sockaddr_storage 能够满足最苛刻的对齐要求。
sockaddr_storage 足够大,能够容纳系统支持的任何套接字地址结构。
注意,除了 ss_len 和 ss_family 外(如果有的话),sockaddr_storage 结构中的其他字段对用户来说是透明的。sockaddr_storage 结构必须类型强制转换成或复制到适合与 ss_family 字段所给出地址类型的套接字地址结构中,才能访问其他字段。
1.5 套接字地址结构比较
四 Share
一个总结了常用的 Unix / Linux 命令的网站,分类比较详细,包括以下类别下的各种具体的命令的用法
文件系统
操作系统
进程管理
网络
IO
压缩
内存管理
快捷键
公众号 加贝君的理想国
12/29/2018
加贝木苇
原文地址:https://www.cnblogs.com/lijianming180/p/12284388.html